cloud-eng .nl

C# 7 features preview

Published on:

Last week my twitter feed exploded with lots of entries about Microsoft //Build 2016 conference. As it’s one of the most important events for .NET dev community MSFT prepared quite a few awesome announcements for us:

Since I got a bit sick this weekend I had plenty of time to play with new VS15 and C# 7.

Getting started

Let’s grab a VS “15” first. There is a new installer by the way.

Enabling experimental features

By default VS15 is using C# 6, so we need to add conditional compilation symbols to our project: __DEMO__.

To do that you should go to Properties > Build > Conditional compilation symbols conditional compilation symbols dialog

Once we’re done with that VS15 will pick up changes automatically.

Features

As of today C# 7 goes with several features:

  • Binary literals
  • Digit separators
  • Local functions
  • Ref returns and locals
  • Pattern matching

Binary literals and Digit separators

These are very minor features, you know, nothing to write home about: In addition to existing int literals such as hex we can use binary ones.

class LiteralsDemo
{
  public void BinaryLiterals()
  {
    var numbers = new[] { 0b0, 0b1, 0b10 };
    foreach (var number in numbers)
    {
      Console.WriteLine(number);
    }
  }
}

Simple and works as expected.

binary literals output

Same about digit separators. Similar feature exists in Java since version 7.

public void DigitSeparators()
{
    var amount = 1_000;
    var thatIsALot = 1_000_000;
    var iAmHex = 0x00_1A0;
    var binary = 0b1_000;
}

Local functions

Local functions can be defined in a scope of a method.

This is something you would do when you need a small helper like this one:

public void RegularMethod2()
{
  Func<int, bool> even = (number) => number % 2 == 0;
  foreach (var number in Enumerable.Range(0, 10).Where(even))
  {
      Console.WriteLine(number);
  }
}

This could be rewritten in the following way now:

class LocalFunctoins
{
    public void RegularMethod()
    {
       bool Even(int number) => number % 2 == 0;

       foreach(var number in Enumerable.Range(0,10).Where(Even))
       {
          Console.WriteLine(number);
       }
     }
}

As you can see local functions support expression bodies, they also can be async.

Local functions can capture variables as lambdas do.

public void Foo(int z)
{
    void Init()
    {
        Boo = z;
    }
    Init();
}

Also might be handy for iterators:

 int[] GetFoos()
 {
     IEnumerable<int> result() // iterator local function
     {
         yield return 1;
         yield return 2;
     }
     return result().ToArray();
 }

Ref returns and locals

Sort of a low-level feature in my opinion. You can return reference from method. Erric Lippert thought that

we believe that the feature does not have broad enough appeal or compelling usage cases to make it into a real supported mainstream language feature.

Not anymore, he-he.

 static void Main()
 {
     var arr = new[] { 1, 2, 3, 4 };
     ref int Get(int[] array, int index)=> ref array[index]; 
     ref int item = ref Get(arr, 1);
     WriteLine(item);
     item = 10;
     WriteLine(arr[1]);
     ReadLine();
 }

Will print:

2
10

Pattern matching

Patterns are used in the is operator and in a switch-statement to express the shape of data against which incoming data is to be compared. Patterns may be recursive so that subparts of the data may be matched against subpatterns.

This is huge. C# community has been waiting for it for a long time. Unfortunately the syntax is not final now.

There are several types of patterns supported for now.

Type pattern

The type pattern is useful for performing runtime type tests of reference types.

public void Foo(object item)
{
    if (item is string s)
    {
        WriteLine(s.Length);
    }
}

Constant Pattern

A constant pattern tests the runtime value of an expression against a constant value.

public void Foo(object item)
{
    switch (item)
    {
        case 10:
            WriteLine("It's ten");
            break;
        default:
            WriteLine("It's something else");
            break;
    }
}

Var Pattern

A match to a var pattern always succeeds. At runtime the value of expression bounds to a newly introduced local variable.

 public void Foo(object item)
 {
     if(item is var x)
     {
         WriteLine(item == x); // prints true
     }
 }

Wildcard Pattern

Every expression matches the wildcard pattern.

 public void Foo(object item)
 {
     if(item is *)
     {
         WriteLine("Hi there"); //will be executed
     }
 }

Recursive Pattern

public int Sum(LinkedListNode<int> root)
{
    switch (root)
    {
        case null: return 0;
        case LinkedListNode<int> { Value is var head, Next is var tail }:
            return head + Sum(tail);
        case *: return 0;
    }
}

Others

switch based patterns could contain so-called guard close:

public void Foo(object item)
{
    switch(item)
    {
        case int i when i > 10:
            WriteLine("That's a good amount");
            break;
        case int i:
            WriteLine("That's fine");
            break;
        default:
            WriteLine("whatever");
            break;
    }      
}

Patterns could be joined:

public void Foo(object item)
{
    if(item is string i && i.Length is int l)
    {
        WriteLine(l > 10);
    } 
}

Conclusion

Pattern matching is really neat. I spent some time with it and I like it.

console installer output

Tags: