Great new features in C# for a returning .NET dev

Chris Noring - Mar 2 '20 - - Dev Community

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

-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);

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);
}

There are two ways we can go about this:

  1. ask for all variables from our tuple
  2. 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"

Discard case

var (name, _) = GetPerson("Chris", "Microsoft");
// name = "Chris"

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" };

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";
      }
    }

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;

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();
}

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;
}

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" 
};

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 value null or the date it was signed
  • SkipLevelBossSigned, has the value null 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"
}

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"
}

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)
}

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;
  }
}

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);
}

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 }; 

 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:

  1. Create a method on a class that returns a Tuple of what I need
  2. 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 :)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .