C# 7.1 and Beyond: Polishing Usability

C# 7.1 and Beyond: Polishing Usability

Last year I talked about the new features of C# 7.0. Visual Studio 2017 has been released a few months ago, so you can now enjoy using these features in your day-to-day work. But Microsoft has, of course, not been idle in the meantime. They’ve detailed what they were, and are, working on in a presentation at BUILD 2017 called The Future of C#.

They explain how they will move forward from C# 7.0. Microsoft will actually start doing ‘point releases’ of the C# language and the compiler. This means they can release incremental improvements as updates to Visual Studio and the build tools. Finally, we won’t have to wait until the Microsoft gods favor us and we are blessed with a new major release!

It also means it might get a bit harder to figure out if a particular language feature is supported. Before, you could simply ask ‘are you using Visual Studio (insert year)?’, which would immediately translate into a C# version. Now, you have to find out which specific update of Visual Studio or the build tools is installed. On the other hand, why would you not install an update?

Still, on the whole, I think it’s great that we get these ‘feature updates’ during the lifetime of a Visual Studio release.

All of the features that I’m going to discuss are still in draft; they might not make it at all or only in a later version.


Well, except this one. This is a feature that I did not discuss in my previous post, but it’s actually already in C# 7.0. A discard is an indication that you are not interested in using a parameter or declared variable.

Imagine that you have a function that returns a tuple (int count, int sum). You are only interested in the count field. Without discards, you would have to do something like this:

(int count, int dummy) = Tally();

Now, of course, this works, but it’s very impractical. If you have more than one of these in a single the first one will declare the variable dummy, while the second one will only use it.

(int count, int dummy) = Tally();
(int average, dummy) = GetStatistics();

Your IDE will probably complain that the value assigned to dummy is not being used in any execution path. Not very pretty. And it gets even worse when you have several ‘dummies’ of different types. dummy1, dummy2, dummy3

Discards solve this by basically making _ a keyword. When you use _, you’re telling the compiler ‘I don’t care about this, don’t even bother creating a variable for it.’ This was already possible for delegate arguments, but when you wanted to ignore multiple arguments, you (obviously) couldn’t reuse names, so you got something like (_, __, ___). Ugly. Since discards are a special case, you can reuse them. It’s much prettier and more readable.

// in deconstruction
(int count, _) = Tally();

// in delegate parameters
var usersWithPosts = 
                 user => user.UserId,
                 post => post.UserId,
                 (user, _) => user);

// in out arguments
int.TryParse(input, out _);

// in pattern matching
switch (input)
    case int number:
    case object[] _:
        // ignore

Thank you, Microsoft, for letting us not care about stuff.

async Task Main() (C# 7.1)

The async and await keywords, introduced in C# 5.0, are an awesome solution to the headache that is asynchronous programming. They make it very easy to write code that calls out to external systems, or does I/O, without blocking a thread. This makes web applications scalable, and keeps UI applications responsive.

However, once a part of your code is asynchronous, everything that’s calling into that code also needs to become asynchronous. When your hosting application is a console application or Windows service, this is a bit of a problem. You can write async void Main, but this means that as soon as you hit your first await, the entire application terminates. This is because async void is a fire-and-forget mechanism. Since you cannot return a Task from Main, you will have to use something like AsyncContext from Stephen Cleary’s excellent AsyncEx library.

Soon, that will no longer be necessary. The compiler and the framework will recognize and properly execute an async Task Main method.

Better null type inference (rumored - C# 7.1?)

Have you ever written a ternary expression that returns a nullable value type and one of the branches returns null? It sucks.

int? x = flag ? 1 : (int?) null;

Why do I need to specify the type explicitly? If you don’t, you get an error stating that there is no implicit conversion between 'int' and '<null>'. In a future version of C#, you won’t have to specify the type explicitly any more.

int? x = flag ? 1 : null;

Much nicer.

default expression (C# 7.11)

This is simply an improvement on something that has been supported since C# 2.0 and the advent of generics, back in 2005. It came in the form of default(T), where T is of course a type. For reference types, it represents null, and for value types such as int, it represents that type’s default, uninitialized, value.

The new feature is that, when the type of T can be inferred, you don’t need to specify it any more.

Instead of writing this:

int x = default(int);

... you can write this:

int x = default;

Of course this also extends to other usages of default, such as return statements, default values for arguments, and, expanding on the previous point, ternary expressions. This will be valid in a future version of C#:

var x = flag ? 1 : default;

That is pretty clean.

Tuple projection initializers (C# 7.1)

I can’t decide if this feature’s name sounds awesome or if it is simply incomprehensible. Anyway, remember tuples from C# 7.0? Let’s say you want to create a tuple (string firstName, string lastName). And let’s say that you already have local variables called firstName and lastName. Right now, you have to write something like this:

var t = (firstName: firstName, lastName: lastName);

This feature infers the names of the tuple elements from their initialization expression, similar to what you can do with anonymous types.

var t = (firstName, lastName);

Console.WriteLine($"Hello, {t.firstName}!");

Simple, and very effective; I like it. One wonders why this wasn’t in there to begin with.

Pattern matching with generics (C# 7.1)

This is another one of those ‘make a good feature even more useful’ things. The whole point of pattern matching is so instead of this:

var objString = obj as string;
if (objString != null)
    Console.WriteLine($"String: {objString}");
else if (obj is int)
    var objInt = (int) obj;
    Console.WriteLine($"Int: {objInt}");

... you can write this:

switch (obj)
    case string s:
        Console.WriteLine($"String: {s}");
    case int i:
        Console.WriteLine($"Int: {i}");

There was a limitation in C# 7.0, however. Consider the following code:

class MyList<T>
    public void Add(T item)
        var stringItem = item as string;
        if (stringItem != null)
            Console.WriteLine($"String: {stringItem}");

If we use as to determine what kind of item it is, that will compile. However, if we use pattern matching with is (or case), it will not.

public void Add(T item)
    if (item is string s)
        Console.WriteLine($"String: {s}");

Pattern matching in C# 7.0 has a requirement stating that there must be an explicit or implicit conversion from the left-hand-side type (T) to the right-hand-side type (string). When either is an open type, the compiler determines that this might not be the case. For instance, the closed type for T might be int, and there is obviously no implicit or explicit conversion from int to string. The C# team have determined this is an omission in the spec. In C# 7.1, the spec will be expanded so that either the left-hand-side or the right-hand-side can be an open type.

But wait, there’s more!

As said, the feature list for C# 7.1 is not yet definitive, but the things Microsoft have lined up right now look very promising and will be sure to add to the expressiveness of C# and, by doing so, make it just a little bit easier to write readable and maintainable code. And isn’t that what we all want?

What do you think of the improvements that are being made? Let me know in the comments.

Next week, we’ll look at some of the more speculative, but also more radical, improvements that are currently scheduled to be included in C# 8.0.

  1. I initially had this as ‘rumored’, but redditor grauenwolf correctly pointed out that the feature is already part of the C# 7.1 Candidate Milestone.