Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News An Early Look at C# 7.1: Part 2

An Early Look at C# 7.1: Part 2

This item in japanese

Yesterday we looked at Async Main and Default Expressions. Our tour of C# 7.1 continues with the proposals titled Infer Tuple Names and Pattern-matching with Generics.

Infer Tuple Names

Developers don’t often think about it, but anonymous types in C# include name inference. For example, if you write the below line then the object y will have properties named A and B.

var y = new { x.A, x.B };

Under the Infer Tuple Names proposal, value tuples would have roughly the same capabilities.

var z1 = (A: x.A, B: x.B); //explicit names
var z2 = (x.A, x.B); //inferred names

There are some notable differences between anonymous types and value tuples:

  • Anonymous types require property names, either explicit or inferred.
  • Value tuples will label unnamed properties Item1, Item2, etc.
  • Anonymous types with duplicate names result in a compiler error.
  • Value tuples with duplicate explicit names result in a compiler error.
  • Value tuples with duplicate inferred names will cause name inference to the skipped. For example (x.A, x.B, y.A) becomes (Item1, B, Item3).
  • Value tuples cannot use the following reserved names: ToString, Rest, ItemN (where N > 0)

An interesting difference between C# and VB is that VB can infer anonymous property names from functions. For example:

var y = new { x.A, x.Bar() }; //compiler error
Dim y = New With {x.A, x.Bar()} //anonymous type {A,Bar}

This capability will be extended to tuples in VB.

This feature can result in a breaking change if you happen to have an extension method with the same name as an inferred property. The proposal continues,

The compatibility council found this break acceptable, given that it is limited and the time window since tuples shipped (in C# 7.0) is short.

Tuple Names in Generic Constraints

While the compiler tries to warns the programmer about tuple name mismatches. For example,

public static (int A, int B) Test1((int A, int B) a)
Test1((A: 1, B: 2));
Test1((X: 1, Y: 2)); //warning, tuple name mismatch

This doesn’t work when you start getting into generic constraints:

public static T Test2<T>(T a) where T : IEnumerable<(int A, int B)>
Test2(new List<(int A, int B)>());
Test2(new List<(int X, int Y)>()); //no warning

The current word is that tuple names in generic constraints cases simply aren’t going to be checked by the compiler. Theoretically the compiler could catch stuff like this, but the performance cost would be too great compared to the benefit.

Pattern-matching with Generics

Pattern matching, new in C# 7.0, has a design flaw when it comes to generics. Consider this code submitted by Alex Wiese:

class Program
    static void Main(string[] args) {}

    public void Send<T>(T packet) where T : Packet
        if (packet is KeepalivePacket keepalive)
            // Do stuff with keepalive
        switch (packet)
            case KeepalivePacket keepalivePacket:
                // Do stuff with keepalivePacket

public class Packet {}

public class KeepalivePacket : Packet {}

This gives us the error: “An expression of type T cannot be handled by a pattern of type KeepalivePacket.” But if we change the parameter to be of type System.Object instead of T, it works as expected.

public void Send(object packet)

In C# 7.1 this has been corrected by slightly changing the rules under which pattern matching can occur,

We change the paragraph in the pattern-matching specification (the proposed addition is shown in bold):

Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type E is said to be pattern compatible with the type T if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from E to T, or if either E or T is an open type. It is a compile-time error if an expression of type E is not pattern compatible with the type in a type pattern that it is matched with.

This is considered to be a bug fix, but since it will “introduce a forward incompatibility” you will have to set the compiler to C# 7.1 in order to take advantage of the change.

Rate this Article