Great new features in C# for a returning .NET dev, deconstruct, pattern matching and records
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
So I've been out of the loop for a while. I've actually been coding full-stack JavaScript and upon returning to .NET I've noticed there have been a few amazing additions to C# language, features that I recognize from JavaScript and Functional languages. That makes me super happy as it means C# is able to pick the raisins from different paradigms and most importantly evolve and become better and better. Additionally seeing .NET Core become faster and better doesn't hurt either 😉 .NET working on all platforms, who would have thought that at the beginning of 2000? 😃
So what are these features?
References
- Deconstructing, Tuples and Objects This covers how to do Deconstructing for Tuples and Objects. It goes through everything you need to know
- Pattern matching .NET 8.0 With .NET 8.0 came some updates to Pattern Matching that was added in C# 7.0
- Pattern matching in general This covers the different types of pattern matching that exists.
- Records - the future isn't here yet, but here's a peek
-1- Deconstruction
This is one feature I've come to love from JavaScript, of course in JavaScript it is called destructuring. So what's the idea? The idea is that you want to refer to a few properties on an object or items in a list but you really don't want to all of it. Let's look at a JS example:
// JavaScript example
function getPerson() {
return {
name: 'chris',
company: 'Microsoft',
title: 'Cloud Advocate'
};
}
// without destructuring
const person = getPerson();
console.log(person.name);
console.log(person.company);
// with destructuring
const { name, company } = getPerson();
console.log(name);
console.log(company);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
As you can see in the above I have to type person
as soon as I just want to talk to a member. A lot of extra characters 😕
How does this look in C# then?
(var pname, var pcompany) = CreatePerson("chris", "microsoft")
Ok, looks similar. What exactly does
CreatePerson()
give back to me?
In this case, it gives back an Object, but it could also be a Tuple.
Ok so this just works or?
Well, depends on what structure you are working on. It works currently on Tuples and Classes, albeit with Classes you need to do a little extra work to get it up and running.
Tuples
Let's go through Tuples first so we understand the ins and outs of it. Let's first create a method that produces a Tuple. Like so:
public static (string, string) GetPerson(string name, string company)
{
return (name, company);
}
2
3
4
There are two ways we can go about this:
- ask for all variables from our tuple
- ask for as many as we want but use a throwaway character
_
Let's demonstrate the first case:
All variables
var (name, company) = GetPerson("Chris", "Microsoft");
// name = "Chris"
// company = "Microsoft"
2
3
Discard case
var (name, _) = GetPerson("Chris", "Microsoft");
// name = "Chris"
2
Above we are using _
to denote that we don't care about the second parameter in the Tuple.
What about if we have 3 or more parameters, would they all just be stored in
_
?
Well no. You would need to declare for each position it's a position we don't care about so that would mean we would type like this:
var (name, _, _) = GetPerson("Chris", "Microsoft");
Classes
We can do Destruct on classes as well and access specific fields just like we do with Tuples. To accomplish that we need however to add a Deconstruct()
method to the class, like so:
public class Person
{
public string Name { get; set; }
public string Company { get; set; }
public string Country { get; set; }
public void Deconstruct(out string name, out string company)
{
name = Name;
company = Company;
}
public void Deconstruct(out string name, out string company, out string country)
{
name = Name;
company = Company;
country = Country;
}
}
// and access like so
(var name, var company) = new Person(){ Name = "Chris", Company ="Microsoft" };
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
The same rule applies here when it comes to positions we are not interested in, i.e use the _
like so:
(var name, var company, _) = new Person(){ Name = "Chris", Company ="Microsoft" };
Above we are saying we are discarding country
.
Overloading Deconstruct
You might have noticed how we in our class Person
have two Deconstruct()
methods. It's because we are overloading and this gives us the ability two write the following:
(var name, var company, _) = new Person(){ Name = "Chris", Company ="Microsoft" };
like so:
(var name, var company) = new Person(){ Name = "Chris", Company ="Microsoft" };
Above we are actually removing our discard, _
, because we are now using the Deconstruct()
with two params.
But wait, when do I need the discard,
_
then?
You need it for one parameter, in the following case:
(var name, _ ) = new Person(){ Name = "Chris", Company ="Microsoft" };
Above we are targeting the Deconstruct()
with two parameters while discarding the last param.
So why can't I just type
( var name )
It's implemented that way, what can I tell ya. Just remember, if using overloading, use 2 or more params to make use of it.
To learn more about Deconstruct have a read here:
https://docs.microsoft.com/en-us/dotnet/csharp/deconstruct
-2- Pattern matching
Pattern matching is something I've seen in functional languages like F#. So what's its purpose? Well if you ask me it's to type less and be more precise in what you actually want to be compared. So what are we talking about, really? Switch-case and how to make those easier to read and more purposeful.
Show me, please
Ok. So I came from a past when C# supported switch case on string, integers, and enums, that was it. Forget about using it on objects, then I would need to do if, else-if and cast the object, etc. Let's show some code so we are on the same page:
public abstract class Character
{
private string _name;
public string Name
{
get {
return _name;
}
}
public Character(string name)
{
_name = name;
}
}
public class Hero: Character
{
public Hero(string name): base("The Good" + name){}
public string Healing()
{
return "Performs healing";
}
}
public class Villain: Character
{
public Villain(string name) : base("The Evil "+ name) { }
public string Lightning()
{
return "Performs Force lightning";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Now imagine we have some code like this:
var hero = new Hero("Luke");
var villain = new Villain("Darth Vader");
Character character = hero;
var ability = string.Empty;
2
3
4
Let's now add some switching logic to this from the C# I used to remember:
if(character is Hero)
{
var h = (Hero) character;
ability = h.Healing();
} else {
var v = (Villain) character;
ability = v.Lightning();
}
2
3
4
5
6
7
8
Are we happy about this? No not really. Good news though, per C# 7 we got the ability to do this:
switch(character)
{
case Hero hero1:
ability = hero1.Healing();
break;
case Villain villain1:
ability = villain1.Lightning();
break;
}
2
3
4
5
6
7
8
9
Above we are creating the object hero1
or villain1
depending on our case. That's a little less to type.
Hold my beer, let's do C# 8.0 😃
ability = character switch {
Hero hero2 => hero2.Healing(),
Villain villain2 => villain2.Lightning(),
_ => "Unknown"
};
2
3
4
5
That looks really nice and compact 😃
Right?
Positional pattern
We were just wow-ed by C# 8, or at least I was 😃 So what else do we have? We call it the positional pattern. This pattern gives us the ability to inspect an object and depending on what properties it has set a certain way we can act accordingly.
Imagine that we have an invoice. Depending on what state it's in we might want to treat it differently, so how do we express this in a switch-case given pattern matching?
Well before we do that let's first say that we will use Destruction to do this. Then the next interesting questions become, what values could be of interest. Let's assume we have the following properties on an Invoice
class BossSigned
, SkipLevelBossSigned
. We make up a business rule that says if the boss and the boss above have signed the Invoice can be processed.
BossSigned
, has the valuenull
or the date it was signedSkipLevelBossSigned
, has the valuenull
or the date it was signed
var result = invoice switch
{
Invoice (null, null) => "Boss need to sign",
Invoice (_ , null) => "Skip level boss need to sign",
_ => "All good, process"
}
2
3
4
5
6
Property pattern
So far we matched on type Invoice
but we can also throw something at it where we don't care about type, just the shape
var result = shape switch
{
{ BossSigned: null, SkipLevelBossSigned: null } => "Boss need to sign",
{ SkipLevelBossSigned: null } => "Skip level boss need to sign",
_ => "All good, process"
}
2
3
4
5
6
The above has a risk though, it will hit the default case if _
has the value null. You might want to fix the code before going to production.
-3- Records
This is something that hasn't landed yet but oh boy am I excited about it. What is it? Well, in short, forget about field declarations
Are you serious? What would I get instead and how would that look?
Ok, so this for me comes from Scala and Typescript, the ability to have heavy lifting done for you, namely the creation of fields. It looks like so in TypeScript:
class Hero {
constructor(private name: String, private hp: number, public shout: String)
}
2
3
The idea above is to have the fields name
, hp
and shout
created for you. So you wouldn't have to type them out like so:
// the above would compile to this
class Hero {
constructor(name: String, hp: number, shout: String) {
this.name = name;
this.hp = hp;
this.shout = shout;
}
}
2
3
4
5
6
7
8
9
As a C# developer, this looks a little backward, with the type last?
It reminds me of this meme, but yea it's just the way it is 😃
How would that look once implemented in C#? Well they are working on it but the proposal should look like this:
public class Hero(String name, int hp, String shout);
which would compile into this:
public class Hero : IEquatable<Hero>
{
public String name;
public int hp;
public String shout;
public Hero(string name, int hp, string shout)
{
this.name = name;
this.hp = hp;
this.shout = shout;
}
public bool Equals(Hero other)
{
//
}
public override bool Equals(object other)
{
//
}
public override int GetHashCode()
{
//
}
public void Deconstruct(out string name, out int hp, out string shout)
{
name = name;
hp = hp;
shout = shout;
}
public Hero With(string name = this.name, int age = this.age, string shout = this.shout) => new Hero(name, hp, shout);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
So we would get a lot extra like Deconstruct, Equal and using this new syntax With
. With
is a new thing too that helps us create new immutable objects. It would allow us to type like this:
var hero = GetHero(); // get a Hero object from somewhere
var newHero = hero.With(hp = 23)
// alt syntax
var otherHero = hero with { hp = 23 };
2
3
4
5
Summary
This is the end of this article. If you are like me coming back to .NET it's the same but different and oh yes .NET Core, of course, is a huge bit of news. Being able to run .NET on Linux/Mac, containerize your code and more. Who thought that day would ever come?
So confession time, would I use these constructs? I would definitely use the pattern matching, even if I would resort to inheritance a lot of the time and avoid this kind of logic.
What about Deconstruct? I mean with Tuples yes, there is no extra effort needed to just have it work. For Objects? I see myself doing one of two things:
- Create a method on a class that returns a Tuple of what I need
- Yes, add
Deconstruct()
method on classes where it makes sense. I would ask myself the following though: should I pass fields from a class to some outer context. Aren't fields an implementation detail? But what about properties? Well we have Data Transfer Objects with a lot of public fields, so there
Records, I need them in my life. Can't wait for them to finish implementing 😃