fooo.ooo

C# 8.0: What is new?

Nov 20, 2018

The aim of this post is to give you an overview of what to expect, and a heads-up on where to expect it. The next major version is C# 8.0. It has been in the works since they shipped the minor C# releases 7.1, 7.2 and 7.3.

The current plan is that C# 8.0 will ship at the same time as .NET Core 3.0. However, the features will start to come alive with the previews of Visual Studio 2019.

The original post can be found here.

csharp logo

New Features in C# 8.0

This is an overview of the most significant features mentioned for C# 8.0.

Nullable reference types

This change is important, because this might break your existing project in the long run when migrating to the newer versions.

[…] It stops you from putting null into ordinary reference types such as stringit makes those types non-nullable! It does so gently, with warnings, not errors.

string s = null; // Compiler warning: Assignment of null to non-nullable reference type
string? s = null; // Ok

void M(string? s)
{
    Console.WriteLine(s.Length); // Compiler warning: Possible null reference exception
    if (s != null)
    {
        Console.WriteLine(s.Length); // Ok
    }
}

The upshot is that C# lets you express your nullable intent and warns you when you do not abide by it.

Async streams

If you want work with continuous streams of results, such as you might get from an IoT device or a cloud service: IAsyncEnumerable<T> streams are there for that.

The language lets you await foreach over these to consume their elements, and yield return to them to produce elements.

async IAsyncEnumerable<int> GetBigResultsAsync()
{
    await foreach (var result in GetResultsAsync())
    {
        if (result > 20) yield return result; 
    }
}

Ranges and indices

They add a type Index. You can create one from an int that counts from the beginning, or with a prefix ^ operator that counts from the end.

Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

They are also introducing a Range type and can be written with a x..y range expression, where x and y both are indexer. You can then index with a Range in order to produce a slice.

var slice = a[i1..i2]; // { 3, 4, 5 }

Default implementations of interface members

As described in the original post:

Once you publish an interface you cannot add members to it without breaking all the existing implementers of it. In C# 8.0 you can provide a body for an interface member.

Thus, if somebody does not implement that member they will just get the default implementation instead.

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
    // Log(Exception) gets default implementation
}

The ConsoleLogger class does not have to implement the Log(Exception) overload of ILogger, because it is declared with a default implementation.

Recursive patterns

Now you can have patterns within patterns.

IEnumerable<string> GetEnrollees()
{
    foreach (var p in People)
    {
        if (p is Student { Graduated: false, Name: string name })
            yield return name;
    }
}

If p is a Student, has not graduated and has a non-null Name, we yield return that name.

Syntax Sugar

There are two more changes listed in the blog post, both of them I would consider to be syntax sugar - not really new, but an easier way to write something.

Switch expressions

var area = figure switch 
{
    Line _      => 0,
    Rectangle r => r.Width * r.Height,
    Circle c    => Math.PI * c.Radius * c.Radius,
    _           => throw new UnknownFigureException(figure)
};

Target-typed new-expressions

In many cases, when you create a new object, the type is already given from context. You can omit their type in those situations.

Point[] ps = { new (1, 4), new (3,-2), new (9, 5) }; // all Points

Platform dependencies

Most of the C# 8.0 language features will run on any version of .NET. However, a few of them have platform dependencies.

Async streams, indexers and ranges all rely on new framework types that will be part of the .NET Standard 2.1. .NET Core 3.0 as well as Xamarin, Unity and Mono will all implement .NET Standard 2.1, but .NET Framework 4.8 will not. This means that the types required to use these features will not be available when you target C# 8.0 to .NET Framework 4.8.

Closing Statement

I think these are very good additions to the C# language, cannot wait to start using them!

Again, check out the original post here for the full description of all the changes.

Thanks for reading this article. I hope you found this helpful. Share your feedback and experience in the comments and subscribe for more content on fooo.ooo.