C# 7.x and 8.0: Uncertainty and Awesomeness

C# 7.x and 8.0: Uncertainty and Awesomeness

Last week I wrote about the new features that Microsoft have planned for the next version of C#, version 7.1. Let’s also take a look at the things they have planned for a bit further out. Strap yourselves in, because this will be a rather long read.

C# 7.2 and 7.3

The next-up versions of the C# language, 7.2 and 7.3, are less clearly defined than 7.1. C# 7.2 will be focused on enabling you to write low-level code without having to resort to the unsafe keyword. There are some features already defined, like read-only references, blittables, and ref-like types. For C# 7.3, the picture is even less clear. The roadmap slide in the BUILD 2017 presentation only mentions ‘Next steps for pattern matching?’.

The presentation also touches on some of the features they’re thinking about for C# 8.0, so let’s have a look at those. I think they’re pretty exciting, but because this is much further out in terms of planning and releasing, many things are still uncertain. Features might change or might not make it at all.

Asynchronous sequences (C# 8.0)

C# 5.0 was all about async and await, as we know. However, one of the scenarios that was left unsupported is enumeration (you know, foreach). In order to use foreach (or the entirety of LINQ) with the result of an asynchronous method, you have to either retrieve all of the results at once, asynchronously, or be content with the fact that enumeration is not asynchronous.

There is a proposal to support this in the language. It would look something like this:

IAsyncEnumerable<SearchResult> results = 
    searchEngine.GetAllResults(query);

foreach await (var result in results) { // ... }

Now this looks simple enough, but for proper support they would also have to support all of this in LINQ query operators, which is pretty large body of code. They could probably use a lot of the work from System.​Interactive, from the Rx project. There’s not a lot of official documentation there, but Bart de Smet’s blog has some interesting information.

For a lot of scenarios, like querying a database, your data provider would also have to support this scenario. It will probably be quite some time before third party data providers will start supporting this, if at all. I’m looking at you, Oracle. The official Oracle driver does not, to this day, support asynchronous operations at all. Never mind all the Async methods returning Task; they don’t even support the old Begin/End pattern of asynchronous operations.

Regardless of the third party buy-in this feature needs in order to be really useful in day-to-day code, it is very nice to see a way you can asynchronously stream in a large set of items of an unknown size, like, for example, a Twitter feed. Right now you have to retrieve the feed page by page. This should be an implementation detail of the Twitter client you’re using and it should not be represented in your code. With asynchronous sequences, you can abstract away this detail, which is very nice.

Asynchronous Dispose (C# 8.0)

Let’s say you have a desktop application that is directly connecting to a database. Yes, I know it’s 2017, but just go along for the example. You begin a transaction and start doing a lot of work there, all asynchronously so you don’t block the UI. Of course your transaction is initialized in a using statement, so that when an exception occurs, it is neatly disposed of, which in the case of a database transaction means it’s rolled back.

If your transaction has affected a large number of records, rolling back might take a while. And since Dispose is a synchronous call, this means your user interface will be frozen while this is happening. Yes, you can do something like this:

IDbTransaction transaction = null;

try
{
    transaction = connection.BeginTransaction();

    // do important work

    transaction.Commit();
}
finally
{
    await Task.Run(() => transaction.Dispose());
}

... but that’s basically hacking around a shortcoming in the Disposable pattern. A feature is proposed for, at this point, C# 8.0, which would make this a lot easier.

using await (var transaction = connection.BeginTransaction())
{
    // do important work

    transaction.Commit();
}

Again, the usefulness of this feature probably depends a lot on third party buy-in for the IAsync​Disposable interface that will be added.

Extension everything (C# 8.0)

This is one I’m pretty excited about. You’re already able to write extension methods that extend a class’s functionality without having to modify it. That’s it though. You’re not able to add static methods or properties of any kind.

The proposal adds new syntax for defining extensions, which makes it possible for you to add, as the feature suggests, anything to a type. The obvious ones are things like instance properties and static methods and properties, but the slide shown at the presentation also shows a static field. Although the slide does not mention it, the proposal mentions that they would (eventually) be able to support instance fields, using the Conditional​Weak​Table class.

Supporting instance fields would mean you would be able to attach a whole new set of features to an existing object, without having to modify it and without the object being able to interfere with that functionality. It sounds nice, but I have some reservations about it. On the one hand, it would be cleaner and easier on the eyes than using composition. On the other hand, like with extension methods, it’s sometimes not always clear what is happening when you’re just looking at the code. You pretty much need an IDE to be able to figure out that something is an extension. Probably it’s best to use this feature sparingly, only when it really makes sense.

The syntax looks a bit Java-ish (or TypeScript-ish), but remember, it’s not final, so it might still improve.

extension Enrollee extends Person
{
    // static field
    static Dictionary<Person, Professor> enrollees = 
        new Dictionary<Person, Professor>();

    // instance method
    public void Enroll(Professor supervisor) =>
        enrollees[this] = supervisor;

    // instance property
    public Professor Supervisor =>
        enrollees.TryGetValue(this, out var supervisor) 
            ? supervisor
            : null;

    // static property
    public static ICollection<Person> Students => enrollees.Keys;

    // instance constructor
    public Person(string name, Professor supervisor)
        : this(name)
    {
        this.Enroll(supervisor);
    }
}

This is a direct transcription of the slide from the BUILD 2017 talk.

Records (C# 8.0)

I can be very short about this: this is awesome. A record type is nothing more than a collection of fields. You only specify the types and names of the fields, and the compiler will do the tedious work of implementing those the right way. The syntax is very simple.

class Person(string First, string Last);

When you see what the compiler expands it to, you’ll see all of the boilerplate you no longer have to implement.

class Person: IEquatable<Person>
{
    public string First { get; }
    public string Last { get; }

    public Person(string First, string Last)
    {
        this.First = First;
        this.Last = Last;
    }

    public void Deconstruct(out string First, out string Last)
    {
        First = this.First;
        Last = this.Last;
    }

    public bool Equals(Person other) =>
        other != null && 
        Equals(First, other.First) && 
        Equals(Last, other.Last);

    public override bool Equals(object other) =>
        (other as Person)?.Equals(this) == true;

    public override int GetHashCode() =>
        (First?.GetHashCode() * 17 + 
         Last?.GetHashCode())
        .GetValueOrDefault();

    public Person With(string First = this.First, 
                       string Last = this.Last) => 
        new Person(First, Last);
}

That is about 33 lines of code you don’t have to write in order to get a nicely functioning DTO class. As I often say, the best line of code is the one you don’t have to write.

Default interface implementations (C# 8.0)

When this feature was introduced, I was pretty skeptical. Why would you ever add implementations to an interface? Why wouldn’t you use an abstract class for that?

The case made in the presentation was a pretty good one, however. Let’s look at a logging library from two perspectives, namely from that of the client application writing log messages and from that of the third party extension library that is implementing a new feature, like logging to FTP. Yes, I know that is a stupid idea. Just go along.

The full interface looks like this:

public interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(LogLevel level, string format, params obj[] arguments);
    void Debug(string message);
    void Debug(string format, params obj[] arguments);
    void Information(string message);
    void Information(string format, params obj[] arguments);
    void Warning(string message);
    void Warning(string format, params obj[] arguments);
    void Error(string message);
    void Error(string format, params obj[] arguments);
}

Now, from the client application’s perspective, the most interesting methods are those with a specific log level, such as Debug, Information, Warning, and Error, and their overloads. Possibly the Log methods are also interesting, but less so.

From the FTP logging feature’s perspective, the only interesting method is the Log(LogLevel, string) method; all of the other methods are basically convenience overloads of that method.

Currently, because it is an interface, an implementation of ILogger must implement all of the methods in the interface. If we add a new method, for example void Error(Exception ex, string format, params object[] arguments), we have broken the contract. All implementations must now be changed to also implement this method.

With default interface implementations, we can define the implementations of the ‘overloads’ in the interface, so that only the Log(LogLevel, string) method is mandatory to implement. It looks like this (edited for brevity):

public interface ILogger
{
    void Log(LogLevel level, string message);

    void Log(LogLevel level, string format, params object[] arguments)
    {
        Log(level, string.Format(format, arguments));
    }

    void Debug(string message)
    {
        Log(LogLevel.Debug, message);
    }

    void Debug(string format, params object[] arguments)
    {
        Log(LogLevel.Debug, string.Format(format, arguments));
    }
}

How this technically works (from what I understand) is that the methods that are actually implemented become virtual methods on the interface. An implementation can override them if it so chooses, but it doesn’t have to. The primary reason, then, for default interface implementations is being able to extend an interface without breaking backwards compatibility. Pretty good stuff.

Nullable reference types

In 1965, the concept of null was first introduced in the Algol W language by Sir Tony Hoare. He famously described this himself as his ‘billion dollar mistake’ and publicly apologized for it. Whether someone else wouldn’t have introduced null if he hadn’t is debatable, but it is undeniable that null reference errors are the cause of many errors.

It would be great if we could make sure that a parameter or property can never be assigned the value null. While there are some partial solutions in C#, such as using Code Contracts or Fody NullGuard, the community has been asking for years for a first-class solution to this problem.

Some have suggested being able to mark a type as not nullable, using !. A variable string a would be nullable, but string! a would not be. If you did attempt to assign null to non-nullable variable, or assign a value from a nullable variable without checking it for null, you would get a compiler error. Now, for variables, the compiler could solve this pretty easily, but for parameters or properties, they would have to find a way to add extra metadata. And there are still cases they couldn’t solve that way, like newing up an array (where all the elements are initially null). They would have to drastically change the .NET type system, which would break a lot of existing code.

So immediately, we are reduced to only generating warnings when doing something that might result in a null reference error. The C# team have, for now, chosen to do the opposite of marking a variable as non-nullable; all reference types will become non-nullable by default, and you can mark the type of a variable as ‘null-safe’ by decorating them with ?, similar to nullable value types. Using a non-nullable variable that might be null (because you didn’t check if it was yet) will result in a warning, as will assigning the value of a nullable variable to one that is non-nullable.

What does this look like?

// bar is nullable because its type is string?
void Foo(string? bar)
{
    string baz = bar; 
    // this will generate a warning because baz is non-nullable 
    // but bar is nullable, and we haven’t checked bar 
    // to not be null
}

To fix this, we simply have to check for null.

void Foo(string? bar)
{
    if (bar == null)
        throw new ArgumentNullException(nameof(bar));

    string baz = bar; 
}

This behavior will be opt-in, because you can be sure this will generate tons of warnings for existing code bases. I think this is a great stride forward in making C# an even more safe language and, for a large part, getting rid of one of the most prolific errors in software.

Summary

Microsoft is really stepping up the language enhancements, and it is great to see that they are so transparent and open about this. If you have something to say about these changes, you can hop on over to the Roslyn site at GitHub and say it! If you make a strong enough case, you might even effect change.

What do you guys think about these new features? Let me know in the comments. For now, that is the final word on this round of new C# features.