With Visual Studio 2019 released, we’re counting down to .NET Core 3.0 and C# 8.0 this fall. Microsoft just announced the plan going forward: .NET 5 (coverage on VentureBeat amongst others).

.NET 5

Unless you’re a long-time enthusiast of .NET, the entire ecosystem of “.NET Framework”/”.NET Core”/”Mono”, various languages (C#/F#/etc.), and associated TLA-soup of libraries is subtle and confusing. Steering things in the same direction and unifying various sundry technologies is a goal for Fall 2020:

  • Unify .NET Framework, .NET Core, and Xamarin/Mono into single platform.
    • .NET Framework and .NET Core become single open-source framework/runtime: “.NET 5”.
    • Mono and CoreCLR (.NET Core runtime) as alternative “runtime experiences”.
  • Java interoperability on all platforms, Objective-C and Swift on some platforms.
  • Improve AOT compilation story.
    • Full AOT needed for iOS, WASM/WebAssembly, and some other platforms.
    • Partial AOT (to reduce startup time and memory usage) combined with JIT (to maximize code support for generics, reflection, etc.) useful.
    • Mono has had AOT for a while, UWP apps have .NET Native, .NET Framework has older NGEN tool.

C# 8.0 switch

We’re (most) excited about “nullable reference types” (to catch some null-related runtime exceptions at compile-time), but there’s other good stuff coming in C# 8. The humble switch was upgraded with pattern matching in C# 7 and gets a few new abilities in C# 8.

Here’s the starting struct from the .NET Blog post by Mads:

class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

Switch expressions allow switch to “return” a value:

// Before (switch as a statement)
string Display(object o)
{
    switch (o)
    {
        case Point p:
            return $"({p.X}, {p.Y})";
        default:
            return "unknown";
    }
}
// After (switch as an expression)
string Display(object o)
{
    return o switch
    {
        Point p => $"({p.X}, {p.Y})",
        _       => "unknown"
    };
}

If the end of a switch expression is reached without making a match then an expression is thrown.

Property patterns allow you to further refine matches based on accessible fields:

// Before (switch as a statement)
string Display(object o)
{
    switch (o)
    {
        case Point p when p.X == 0 && p.Y == 0:
            return "origin";
        //...
    }
}
// After (switch as an expression)
string Display(object o)
{
    return o switch
    {
        Point { X: 0, Y: 0 } /* p */         => "origin",
        Point { X: var x, Y: var y } /* p */ => $"({x}, {y})",
        {}                                   => o.ToString(), // `o` is non-null and not a `Point`
        null => "null"
    };
}

Positional patterns work with tuples and types- like Point- that have a Deconstruct() method (see deconstructors):

string Display(object o)
{
    return o switch
    {
        Point(0, 0)         => "origin",
        Point(var x, var y) => $"({x}, {y})",
        //...
    }
}

// General case for tuples
string DisplayXY(int x, int y, bool isValid)
    (x, y, isValid) switch {
        (_, _, false) => "invalid",
        (0, 0, true) => "origin",
        //...
    }

Overall, I’m not thrilled about XYZ switch syntax instead of switch XYZ, but these are otherwise welcome additions (especially coming from other languages with a similar feature).

For a longer write up also see this recent MSDN magazine article.