Preface
Goal: A practical case to collect unique record fields using C#.
With C# 9.0
, we have OOP in high level, and FP everywhere else.
For linux user, I give additional setup procedures. But don’t worry, this should be quick. And this is not a cumbersome process. And after that you can have so much fun with the language itself.
Reference Reading
Source Examples
You can obtain source examples here:
Common Use Case
Task: Get the unique tag string
Please read overview for more detail.
Prepopulated Data
Songs and Poetry
using System;
#nullable enable
public record MySong {
public string Title { get; init; } = null!;
public string[]? Tags { get; init; } = null!;
};
class MySongs
{
public MySong[] songs;
public MySongs() {
songs = new [] {
new MySong {
Title = "Cantaloupe Island",
Tags = new string[] {"60s", "jazz"}
},
new MySong {
Title = "Let It Be",
Tags = new string[] {"60s", "rock"}
},
new MySong {
Title = "Knockin' on Heaven's Door",
Tags = new string[] {"70s", "rock"}
},
new MySong {
Title = "Emotion",
Tags = new string[] {"70s", "pop"}
},
new MySong {
Title = "The River",
Tags = null
},
};
}
}
C# Solution
The Answer
There might be many ways to do things in C#
.
One of them is this oneliner as below:
using System;
using System.Linq;
class Step09Unique
{
public void Show() {
MySongs mySongs = new MySongs();
var tags = (mySongs.songs)
.Select(song => song.Tags)
.Where(tags => tags != null)
.SelectMany(tag => tag)
.Distinct();
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
Enough with introduction, at this point we should go straight to coding.
Environment
We are using C# 9.0
for this project.
Install
This case example require C#
9.0 that is shipped with .NET 5.0
.
Just in case your package manager in linux is still using .NET 3.1
,
you can download the binary, extract the tar.gz
,
and make an alias to .bashrc
/zshrc
to dotnet
exectuable.
I’m a linux user, and I can say .NET
is doing it right.
Better and get better.
Project Directory
One installed properly we can make a project straight away.
$ cd /home/epsi/Documents/songs/csharp
$ dotnet new console -o SongsApp
Getting ready...
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on SongsApp/SongsApp.csproj...
Determining projects to restore...
Restored /home/epsi/Documents/songs/csharp/SongsApp/SongsApp.csproj (in 186 ms).
Restore succeeded.
Project File
If a project was made from previous .NET
version.
The project file should be altered to provide C# 9.0
.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp5.0</TargetFramework>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
</Project>
Working with Multiple Script.
I need multiple script to explain this case example.
I love how mature the .NET
has become. But it has tradeoff.
We no longer can affor multiple entry point in one project.
This is my workaround to work with multiple script, in just one project folder. So I guess my problem is solved.
Main Method
.NET
use csc
to compile the code.
Code compiled should have static main
method as an entry point.
class Program
{
static void Main(string[] args) {
…
}
}
Multiple Entry Point
In the past you can use csc
to compile one script,
and you can still do it with mono
.
This means you can have many script with,
each script has their own main method.
This no longer valid with newer roslyn csc
.
We can have as many script as we have,
but there can only be one static main
method.
This is a good thing actually,
and I would like to follow the .Net
direction as possible.
Instead of doing different command line to run different script, I choose to alter the main script, to call different script. This way we can work with multiple class with each different script.
class Program
{
static void Main(string[] args) {
// Step01Tags step = new Step01Tags();
…
Step09Unique step = new Step09Unique();
step.Show();
}
}
I can just switch from different script easily this way.
Using REPL
Alternatively, you can use REPL
with csi
.
1: Class and Record
We are going to use array
throught out this article
Namespace
For simplicity, I strip out all namespaces. Namespace is good for a real project, we should do it a lot. But for this simple step by step case example, we need to simplified a bit.
So here we are just class and method(s).
class Program
{
static void Main(string[] args) {
Step01Tags step = new Step01Tags();
step.Show();
}
}
From the very start, we separate class in different file.
Simple Array
Before building a record
,
I begin with simple array
with fix length string[5]
as below code:
using System;
class Step01Tags
{
private string[] tags;
public Step01Tags() {
tags = new string[5]
{"rock", "jazz", "rock", "pop", "pop"};
}
public void Show() {
Console.WriteLine("[{0}]",
string.Join(", ", tags));
}
}
It is easy to dump variable in C#
using Console.WriteLine
.
This require us to declare using System;
in the first place.
Now we can have the result similar as below array
:
$ cd /home/epsi/Documents/songs/csharp
$ cd SongsApp
$ dotnet run
[rock, jazz, rock, pop, pop]
The Record Type
C# 9.0 New Feature.
We can continue our journey to use record
structure type.
Record is a new feature from C# 9.0
.
So this script won’t run with previous C#
.
Our song record
would be as simple as below code:
public record MySong {
public string Title { get; init; }
public string[] Tags { get; init; }
};
This record
is very similar with class
,
but simplified for easy to use.
The Song Record
How do I populate a record?
The complete class is as below:
using System;
class Step02Type
{
public record MySong {
public string Title { get; init; }
public string[] Tags { get; init; }
};
private MySong song;
public Step02Type() {
song = new MySong {
Title = "Cantaloupe Island",
Tags = new string[] {"60s", "jazz"}
};
}
public void Show() {
Console.WriteLine("[{0}, ({1})]",
song.Title, string.Join(", ", song.Tags)
);
}
}
In order to run this class,
do not forget to alter the entry point.
It is the static main
method.
class Program
{
static void Main(string[] args) {
// Step01Tags step = new Step01Tags();
Step02Type step = new Step02Type();
step.Show();
}
}
With the result similar as below record:
$ dotnet run
[Cantaloupe Island, (60s, jazz)]
We are going to use string.Join
throughout this article.
Class Summary
Here we have four items in a class.
- Record Type:
MySong
, - Property:
song
, - Constructor:
Step02Type()
, - Method:
Show()
.
class Step02Type
{
public record MySong {…};
private MySong song;
public Step02Type() {…}
public void Show() {…}
}
Take a deep breath. Take your caffeine. There are some interesting material ahead.
2: Data Structure
From just karaoke, we can go pro with recording studio. From simple data, we can build a structure to solve our task.
Array of Song Record
Meet The Songs Array
using System;
class Step03Songs
{
public record MySong {
public string Title { get; init; }
public string[] Tags { get; init; }
};
private MySong[] songs;
public Step03Songs() {
songs = new [] {
new MySong {
Title = "Cantaloupe Island",
Tags = new string[] {"60s", "jazz"}
},
…
new MySong {
Title = "The River",
Tags = new string[] {}
},
};
}
public void Show() {
Array.ForEach(songs,
song => Console.WriteLine("[{0}, ({1})]",
song.Title, string.Join(", ", song.Tags))
);
}
}
with the result similar as below sequential lines of MySong
record:
$ dotnet run
[Cantaloupe Island, (60s, jazz)]
[Let It Be, (60s, rock)]
[Knockin' on Heaven's Door, (70s, rock)]
[Emotion, (70s, pop)]
[The River, ()]
How does it Works?
It is more convenience to show the result using Array.ForEach
,
followed by lambda
function.
public void Show() {
Array.ForEach(songs,
song => Console.WriteLine("[{0}, ({1})]",
song.Title, string.Join(", ", song.Tags))
);
}
The last record is empty string.
new MySong {
Title = "The River",
Tags = new string[] {}
Maybe Null
I also add Nullable
option so Tags
can accept null
value,
instead of just empty list.
using System;
#nullable enable
class Step04Options
{
public record MySong {
public string Title { get; init; } = null!;
public string[]? Tags { get; init; } = null!;
};
private MySong[] songs;
public Step04Options() {
songs = new [] {
new MySong {
Title = "Cantaloupe Island",
Tags = new string[] {"60s", "jazz"}
},
…
new MySong {
Title = "The River",
Tags = null
},
};
}
public void Show() {
Array.ForEach(songs,
song => Console.WriteLine("[{0}, ({1})]",
song.Title, string.Join(", ",
(song.Tags?? new string[] {})))
);
}
}
with the result similar as below sequential lines of MySong
record:
❯ dotnet run
[Cantaloupe Island, (60s, jazz)]
[Let It Be, (60s, rock)]
[Knockin' on Heaven's Door, (70s, rock)]
[Emotion, (70s, pop)]
[The River, ()]
Using Nullable
We need to be examine this nullable
carefully,
or we may end up with compilation errors and warnings.
The most important part is we have to add pragma.
#nullable enable
Then we can add the ?
operator in record
type.
public record MySong {
public string Title { get; init; } = null!;
public string[]? Tags { get; init; } = null!;
};
Now we can declare null data for the last record.
new MySong {
Title = "The River",
Tags = null
And to to show, we are still using Array.ForEach
,
followed by lambda
function.
Array.ForEach(songs,
song => Console.WriteLine("[{0}, ({1})]",
song.Title, string.Join(", ",
(song.Tags?? new string[] {})))
);
But this time we add default value.
(song.Tags?? new string[] {})
This would result empty array whenever the field is null.
Separating Class
Since we need to reuse the songs record multiple times, it is a good idea to separate the record from logic.
using System;
#nullable enable
public record MySong {
public string Title { get; init; } = null!;
public string[]? Tags { get; init; } = null!;
};
class MySongs
{
public MySong[] songs;
public MySongs() {
songs = new [] {
new MySong {
Title = "Cantaloupe Island",
Tags = new string[] {"60s", "jazz"}
},
new MySong {
Title = "Let It Be",
Tags = new string[] {"60s", "rock"}
},
new MySong {
Title = "Knockin' on Heaven's Door",
Tags = new string[] {"70s", "rock"}
},
new MySong {
Title = "Emotion",
Tags = new string[] {"70s", "pop"}
},
new MySong {
Title = "The River",
Tags = null
},
};
}
}
Using MySongs Class
Now we can have a very short code.
using System;
class Step05Class
{
public void Show() {
MySongs mySongs = new MySongs();
MySong[] songs = mySongs.songs;
Array.ForEach(songs,
song => Console.WriteLine("[{0}, ({1})]",
song.Title, string.Join(", ",
(song.Tags?? new string[] {})))
);
}
}
What is Next 🤔?
We are still far from finished. We need to conclude.
Consider continue reading [ C# - Playing with Records - Part Two ].