C# 9.0 Preview New Features

ahmedwyousif

Ahmed Yousif

Posted on June 5, 2020

C# 9.0 Preview
New Features

Alt Text

C# 9.0 New Features

In past five years, Microsoft had made a rapid enhancements in C# by introducing a plenty major features with every new version, As planed, C# 9.0 will be officially released with .NET 5 on November 2020. So we will dive into C# 9.0 new features that released on 20 may 2020 as a Preview.

1- Top-level programs

Presently, in C# coding a simple program requires a noticeable amount as initial code as following :

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}
Enter fullscreen mode Exit fullscreen mode

C# 9.0 introduces Top-level programs feature that, allows you to code your main program (Any statement is allowed) at the top level instead immediately after using statements and no need to declare any namespace, class or main method.As per C#, you must have only one entry point for your program so, you can exclusively do this in only one file as following:

using System;

Console.WriteLine("Hello World!");
Enter fullscreen mode Exit fullscreen mode

2- Target-typed new expressions

new expressions always required a type to be specified (except for implicitly typed array or anonymous types) as following.

// Instantiation of point object
Point point = new Point(3, 5);

// Instantiation of dictionary object
Dictionary<string, List<int>> field = new Dictionary<string, List<int>>() {
    { "item1", new List<int>() { 1, 2, 3 } }
};

// implicitly typed array
var items = new[] { 10, 20, 30 };

// Instantiation of anonymous types
var example = new { Greeting = "Hello", Name = "World" };
Enter fullscreen mode Exit fullscreen mode

In C# 9.0 the type is optional if there’s a clear target type that the expressions is being assigned to as following:

// initialization without duplicating the type.
Point point = new(3, 5);
Dictionary<string, List<int>> field = new() {
    { "item1", new() { 1, 2, 3 } }
};

// the type can be inferred from usage.
XmlReader.Create(reader, new() { IgnoreWhitespace = true });

Enter fullscreen mode Exit fullscreen mode

3- Pattern Matching Improvements

C# 7 introduced basic pattern matching features then, C# 8 extended it with new expressions and patterns. By the time, C# 9.0 introduced new pattern enhancements as following:

declaring default identifier for type matching not required

// Before
vehicle switch
{
    Car { Passengers: 0}  => 2.00m + 0.50m,
    Car { Passengers: 1 } => 2.0m,
    Car { Passengers: 2}  => 2.0m - 0.50m,
    Car _                 => 2.00m - 1.0m,
};
Enter fullscreen mode Exit fullscreen mode
// C# 9.0
vehicle switch
{
    Car { Passengers: 0}  => 2.00m + 0.50m,
    Car { Passengers: 1 } => 2.0m,
    Car { Passengers: 2}  => 2.0m - 0.50m,
    // no identifier for default type matching
    Car                   => 2.00m - 1.0m,
};
Enter fullscreen mode Exit fullscreen mode

Relational patterns

C# 9.0 introduces supporting the relational operators <, <=, >, and >= patterns as following:

public static LifeStage LifeStageAtAge(int age) => age switch
{
   < 0 =>  LiftStage.Prenatal,
   < 2 =>  LifeStage.Infant,
   < 4 =>  LifeStage.Toddler,
   < 6 =>  LifeStage.EarlyChild,
   < 12 => LifeStage.MiddleChild,
   < 20 => LifeStage.Adolescent,
   < 40 => LifeStage.EarlyAdult,
   < 65 => LifeStage.MiddleAdult,
      _ => LifeStage.LateAdult,
};
Enter fullscreen mode Exit fullscreen mode

Logical patterns

C# 9.0 introduces supporting the logical operators and, or, and not patterns as following:

public static LifeStage LifeStageAtAge(int age) => age switch
{
   <2            =>  LiftStage.Infant,
   >= 2 and <12  =>  LifeStage.Child,
   >= 12 and <18 => LifeStage.Teenager,
   >= 18         => LifeStage.Adult,
};

bool IsValidPercentage(object x) => x is
    >= 0  and <= 100  or    // integer tests
    >= 0F and <= 100F or  // float tests
    >= 0D and <= 100D;    // double tests

Enter fullscreen mode Exit fullscreen mode

Also not is going to be convenient in if-conditions containing is-expressions where, instead of unwieldy double parentheses.

if (!(e is null)) { ... }

if (e is not null) { ... }
Enter fullscreen mode Exit fullscreen mode

4- Covariant return types

By introducing covariant return types feature in C# 9.0, you are permitted to override a method or a read-only property to return a more derived return type than the method or property overridden as following:

class Compilation ...
{
    virtual Compilation CreateWithOptions(Options options)...
}

class CSharpCompilation : Compilation
{
    override CSharpCompilation CreateWithOptions(Options options)...
}
Enter fullscreen mode Exit fullscreen mode

5- Extending Partial Methods

C# has limited support for developers splitting methods into declarations and definitions/implementations.

Partial methods have several restrictions:
1- Must have a void return type.
2- Cannot have ref or out parameters.
3- Cannot have any accessibility (implicitly private).

One behavior of partial methods is that when the definition is absent then the language will simply erase any calls to the partial method .

partial class D
{
    partial void M(string message);

    void Example()
    {
        M(GetIt()); // Call to M and GetIt erased at compile time
    }
    string GetIt() => "Hello World";
}
Enter fullscreen mode Exit fullscreen mode

C# 9.0 extends partial methods to remove most of the existing restrictions around partial methods as following:

1-allow them have ref or out.
2-allow non-void return types
3-allow any type of accessibility (private, public, etc ..).

Such partial declarations would then have the added requirement that a definition must exist. That means the language does not have to consider the impact of erasing the call sites.

When a partial method has an explicit accessibility modifier though the language will require that the declaration has a matching definition even when the accessibility is private:

partial class C
{
    // Okay because no definition is required here
    partial void M1();

    // Okay because M2 has a definition
    private partial void M2();

    // Error: partial method M3 must have a definition
    private partial void M3();
}

partial class C
{
    private partial void M2() { }
}
Enter fullscreen mode Exit fullscreen mode

Further the language will remove all restrictions on what can appear on a partial method which has an explicit accessibility. Such declarations can contain non-void return types, ref or out parameters, extern modifier, etc... These signatures will have the full expressivity of the C# language.

partial class D
{
    // Okay
    internal partial bool TryParse(string s, out int i); 
}

partial class D
{
    internal partial bool TryParse(string s, out int i) { ... }
}
Enter fullscreen mode Exit fullscreen mode

6- Init-only properties

In C#, it is not possible to initialize immutable properties of a class by Object initialize as following.

// immutable properties 
public class Person
{
    public string FirstName { get; }
    public string LastName { get;}
}

// can't be initialized using Object initialize 
// var person = new Person
// {
//    FirstName = "Ahmed",
//   LastName = "Yousif"
// }
Enter fullscreen mode Exit fullscreen mode

on the other, hand if we want to initialize them using Object initialize we have to make these properties mutable that, open the door for manipulating it as following:

// mutable properties 
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
...
var person = new Person
{
    FirstName = "Ahmed",
    LastName = "Yousif"
}

// property can be changed
person.FirstName = "Mohamed"

Enter fullscreen mode Exit fullscreen mode

By introducing Init-only properties C# 9.0 solve that hard equation!
C# 9.0 introduces an init accessor that allow initializing property only during object initialization as following:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

...
var person = new Person
{
    FirstName = "Ahmed",
    LastName = "Yousif"
}

// manipulating property after initialization won't be allowed
// person.LastName = "Mohamed"

Enter fullscreen mode Exit fullscreen mode

7- Records

Init-only properties is a great feature for making individual properties immutable.But what if we want to make the whole object immutable, Here we can highlight the importance of introducing Record feature. by using data keyword before class keyword as following:

public data class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

So, in this case we immuted the whole object which, consider as declaring a record state. records can be shorthand the declaration as following:

// exactly the same declaration before
public data class Person { string FirstName; string LastName; }
Enter fullscreen mode Exit fullscreen mode

If you want to add a private field,you must add private modifier explicitly:

private string firstName;
Enter fullscreen mode Exit fullscreen mode

8- With-expressions

Now, if we want to change the state of immutable object how we can do it! in this case we have to copy the exist object with updated values to represent a new state. by introducing With-expressions feature we can achieve it so easy as following:

var person = new Person
{
    FirstName = "Ahmed",
    LastName = "Yousif"
}

// copy person object with update LastName
var anotherPerson = person with { LastName = "Ali" }; 

Enter fullscreen mode Exit fullscreen mode

With-expressions feature uses object initializer syntax to set state what’s different in the new object.



9- Value-based equality

Value-based equality feature allows you to compare two instances of Record Class value base equality same as Structs by comparing all properties in the object using Object.Equals(object, object) static method. on the other hand if you want check reference base equality you can use Object.ReferenceEquals(object, object) static method as following:

var person = new Person
{
    FirstName = "Ahmed",
    LastName = "Yousif"
}

// copy person object and with same lastname
var anotherPerson = person with { LastName = "Yousif" }; 

// they have the same values so will be true
// Equals(person, anotherPerson)

// they aren’t the same object so will be false
// ReferenceEquals(person, anotherPerson)

Enter fullscreen mode Exit fullscreen mode

10- Positional records

Let's say we want to apply positional approach to a record class
to get benefits of positional deconstruction so, in this case you have to explicitly implement constructor and deconstructor of the record as following:

public data class Person 
{ 
    string FirstName; 
    string LastName; 
    public Person(string firstName, string lastName) 
      => (FirstName, LastName) = (firstName, lastName);
    public void Deconstruct(out string firstName, out string lastName) 
      => (firstName, lastName) = (FirstName, LastName);
}
Enter fullscreen mode Exit fullscreen mode

But there's a good news by introducing C# 9.0 there is a shorthand expressing exactly the same thing.

// equivalent to previous one 
public data class Person(string FirstName, string LastName);


Enter fullscreen mode Exit fullscreen mode

now you can deconstructing your object as following:

var person = new Person("Ahmed", "Yousif"); // positional construction
var (fName, lName) = person;              // positional deconstruction
Enter fullscreen mode Exit fullscreen mode

And much more features…

So, the best place to check out last updates of upcoming features for C# 9.0 is the Language Feature Status on the C# compiler Github repo.

💖 💪 🙅 🚩
ahmedwyousif
Ahmed Yousif

Posted on June 5, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related