New Features in C# 7.0

Microsoft have release C# 7.0 and it comes with a number of new features and brings a focus on data consumption, code simplification and performance. Tuples have been around for a while but they have made them so much easier to use, which make it easy to have multiple results, and pattern matching which simplifies code that is conditional on the shape of data. But there are many other features big and small.

Tuples

It is common to want to return more than one value from a method. The options available in older versions of C# are less than optimal:

  • Out parameters: Use is clunky (even with the improvements described above), and they don’t work with async methods.
  • System.Tuple<...> return types: Verbose to use and require an allocation of a tuple object.
  • Custom-built transport type for every method: A lot of code overhead for a type whose purpose is just to temporarily group a few values.
  • Anonymous types returned through a dynamic return type: High performance overhead and no static type checking.

To do better at this, C# 7.0 adds tuple types and tuple literals:

(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}

The method now effectively returns three strings, wrapped up as elements in a tuple value.

The caller of the method will receive a tuple, and can access the elements individually:

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

Item1 etc. are the default names for tuple elements, and can always be used. But they aren’t very descriptive, so you can optionally add better ones:

(string first, string middle, string last) LookupName(long id) // tuple elements have names

Now the recipient of that tuple have more descriptive names to work with:

var names = LookupName(id);
WriteLine($"found {names.first} {names.last}.");

You can also specify element names directly in tuple literals:

return (first: first, middle: middle, last: last); // named tuple elements in a literal

Generally you can assign tuple types to each other regardless of the names: as long as the individual elements are assignable, tuple types convert freely to other tuple types.

Tuples are value types, and their elements are simply public, mutable fields. They have value equality, meaning that two tuples are equal (and have the same hash code) if all their elements are pairwise equal (and have the same hash code).

This makes tuples useful for many other situations beyond multiple return values. For instance, if you need a dictionary with multiple keys, use a tuple as your key and everything works out right. If you need a list with multiple values at each position, use a tuple, and searching the list etc. will work correctly.

Tuples rely on a family of underlying generic struct types called ValueTuple<...>. If you target a Framework that doesn’t yet include those types, you can instead pick them up from NuGet:

  • Right-click the project in the Solution Explorer and select “Manage NuGet Packages…”
  • Select the “Browse” tab and select “nuget.org” as the “Package source”
  • Search for “System.ValueTuple” and install it.

Pattern matching

C# 7.0 introduces the notion of patterns, which, abstractly speaking, are syntactic elements that can test that a value has a certain “shape”, and extract information from the value when it does.

Examples of patterns in C# 7.0 are:

  • Constant patterns of the form c (where c is a constant expression in C#), which test that the input is equal to c
  • Type patterns of the form T x (where T is a type and x is an identifier), which test that the input has type T, and if so, extracts the value of the input into a fresh variable x of type T
  • Var patterns of the form var x (where x is an identifier), which always match, and simply put the value of the input into a fresh variable x with the same type as the input.

This is just the beginning – patterns are a new kind of language element in C#.

In C# 7.0 they are enhancing two existing language constructs with patterns:

  • is expressions can now have a pattern on the right hand side, instead of just a type
  • case clauses in switch statements can now match on patterns, not just constant values

In future versions of C# they are likely to add more places where patterns can be used.

Is-expressions with patterns

Here is an example of using is expressions with constant patterns and type patterns:

public void PrintStars(object o)
{
    if (o is null) return;     // constant pattern "null"
    if (!(o is int i)) return; // type pattern "int i"
    WriteLine(new string('*', i));
}

As you can see, the pattern variables – the variables introduced by a pattern – are similar to the out variables described earlier, in that they can be declared in the middle of an expression, and can be used within the nearest surrounding scope. Also like out variables, pattern variables are mutable. We often refer to out variables and pattern variables jointly as “expression variables”.

Patterns and Try-methods often go well together:

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

Switch statements with patterns

We’re generalizing the switch statement so that:

  • You can switch on any type (not just primitive types)
  • Patterns can be used in case clauses
  • Case clauses can have additional conditions on them

Here’s a simple example:

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

There are several things to note about this newly extended switch statement:

  • The order of case clauses now matters: Just like catch clauses, the case clauses are no longer necessarily disjoint, and the first one that matches gets picked. It’s therefore important that the square case comes before the rectangle case above. Also, just like with catch clauses, the compiler will help you by flagging obvious cases that can never be reached. Before this you couldn’t ever tell the order of evaluation, so this is not a breaking change of behavior.
  • The default clause is always evaluated last: Even though the null case above comes last, it will be checked before the default clause is picked. This is for compatibility with existing switch semantics. However, good practice would usually have you put the default clause at the end.
  • The null clause at the end is not unreachable: This is because type patterns follow the example of the current is expression and do notmatch null. This ensures that null values aren’t accidentally snapped up by whichever type pattern happens to come first; you have to be more explicit about how to handle them (or leave them for the default clause).

Pattern variables introduced by a case ...: label are in scope only in the corresponding switch section.

Literal improvements

C# 7.0 allows _ to occur as a digit separator inside number literals, I’m not a big fan of this, but I thought it worth mentioning here:

var d = 123_456;
var x = 0xAB_CD_EF;

You can put them wherever you want between digits, to improve readability. They have no effect on the value.

Also, C# 7.0 introduces binary literals, so that you can specify bit patterns directly instead of having to know hexadecimal notation by heart.

var b = 0b1010_1011_1100_1101_1110_1111;

These are just a few new features of C# 7.0, to see more view Mads Torgersen blog entry

Or watch the short video of the new features: