C# 7.0 Features

C# 7.0 Features
   With C# 7.0 now released, it is time to share my opinion, also known as the correct opinion, on the new features. People who are too lazy or uninformed to form their own opinion can safely use mine. I will not charge you for that. Sharing my opinion is absolutely free. If you are interested in more impartial overview of the features (i.e. if you really want to form your own opinion and risk being wrong) you can read Microsoft's post on the topic and the Stack Overflow documentation article. So let's begin...

Tuples


   Tuples are probably the most significant new feature C# 7.0 has to offer. A tuple is an ordered set of values. A tuple does not have to be declared in advance like a class or a struct. It is created inline using parenthesis.

(5, "Gosho")

Is a tuple of int and string. If we need to declare it we do it like this

(int, string) personInfo = (5, "Gosho");

We can access the values like this

Console.WriteLine($"Age:{personInfo.Item1}, Name:{personInfo.Item2}");

This is of course not very pleasant but we can give the tuple elements their own names.

(int age, string name) personInfo = (5, "Gosho");

Or like this

var personInfo = (age: 3, name: "Gosho");

So the usage becomes

Console.WriteLine($"Age:{personInfo.age}, Name:{personInfo.name}");

   One way to think of Tuples is like an object representing a parameter list. Parameter lists are an ordered set of values. They do have names but the names are not important. What is important is the type and the order of the parameters.

   Internally Tuples are represented by the ValueTuple types which are (Surprise!) Value Types. There are ValueTuple classes with Item1 to Item7 elements. Tuples with more elements are represented as a Tuple with 7 elements where the last element is a Tuple. In this way the compiler can chain as much as it likes. Unlike anonymous types Tuples can be returned from methods and their items can be access in a statically typed manner.

   One interesting question is how Tuples returned from methods (or properties, or fields, etc.) preserve the names of their items especially across assemblies, because the ValueTuple type itself does not keep information about the names. The different items of Tuple type are decorated with the TupleNamesAttribute which lists the names so the compiler can restore them when it calls the method.

Deconstruction


   While the usage above probably has some uses, the main way to use Tuples would be to deconstruct them. Let's redesign the Int32.TryParse method to return a value indicating success and the actual value. With C# 7 we may declare it like this

(bool isValid, int value) TryParse(string source)

To use it we can deconstruct the Tuple into variables.

(bool success, int value) = Int32.TryParse(...);
if(success)
{
   ConsoleWriteLine(value);
}

   Deconstruction introduces variables for the tuple elements. You can also deconstruct into existing variables. Normally the variable names will match the Tuple item names but I specifically changed one of the names to demonstrate that the names do not matter. What matters is the order and the type. Interestingly you cannot deconstruct one element in an existing variable and another in a new variable.

   Internally deconstruction uses a Deconstruct method with out parameters which allows us to use deconstruction not only with Tuples but with other types if we want to. I will just copy Microsoft's example here

class Point
{
   public int X { get; }
   public int Y { get; }
   public Point(int x, int y) { X = x; Y = y; }
   public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);

In case you do not care about a value you can use the placeholder _ like this

(int myX, _) = GetPoint();

Out Variables


   Back to the TryParse method of Int32 we find another minor but pleasant improvement. We can now declare variable while passing it as an out parameter.

if(Int32.TryParse(out int i)
{
   Console.WriteLine(i);
}

Just like with deconstructuring we can now pass _ in place of out parameters we do not care about.
This feature will be heartedly applauded by all the COM and Automation-loving folks out there.

Local Functions


   Another minor improvement is the ability to declare local functions. Local functions are declared inside other methods and can capture local variables in a closure. Here is an example

public static double GetCircleArea(double radius)
{
   ThrowIfRadiusInvalid();
   return Math.PI * radius * radius;

   void ThrowIfRadiusInvalid()
   {
       if(radius < 0) //no allocation here the closure object is ValueType because it never leaves the scope
       {
           throw new ArgumentException(nameof(radius));
       }
   }
}

Now this is probably a stupid way to do validation but as I said the feature is a minor thing. Note how you can define the function at the bottom and use it all over the enclosing method. Previously you could do something similar with delegates but you had to put the declaration before the usage.

public static double GetCircleArea(double radius)
{
   //two additional allocations, one for the delegate and one for the closure object
   Action throwIfRadiusInvalid = () =>
   {
       if (radius < 0)
       {
           throw new ArgumentException(nameof(radius));
       }
   };

   throwIfRadiusInvalid();
   return Math.PI * radius * radius;
}

   Note that the old method requires two additional allocations one for the delegate object and one for the closure. We can improve this even further by removing the allocations altogether. In the case with local functions the allocation for the closure is avoided by using a value type to pass the parameters to the local function.  If you enclose parameters in both a lambda and a local function, then the local function uses the closure class, created for the lambda.

public static double GetCircleArea(double radius)
{
   ThrowIfRadiusInvalid(radius);
   return Math.PI * radius * radius;

   void ThrowIfRadiusInvalid(double r)
   {
       if (r < 0) //no additional alocations, the argument is passed explicitly
       {
           throw new ArgumentException(nameof(radius));
       }
   }
}

   Here we pass the argument explicitly and therefore there are no allocations (the alternative delegate code would still have one allocation). Removing allocations is one of the main reasons this feature was added. I guess this is important for game developers and other people who use C# but have performance requirements. The other reason is convenience. The delegate version is kind of ugly. Finally, some C# features are not available with lambdas. For example this feature will simplify writing iterators.

public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
   if (source == null) throw new ArgumentNullException(nameof(source));
   if (filter == null) throw new ArgumentNullException(nameof(filter));

   return Iterator();

   IEnumerable<T> Iterator()
   {
       foreach (var element in source)
       {
           if (filter(element)) { yield return element; }
       }
   }
}

   Note that if we simply skipped the local function then the argument checks would happen when calling MoveNext instead of when calling Filter (i.e GetEnumerator) which is undesirable.

   Overall local functions are nice to have but it will be good to have some way to enforce a standard practice. I would not be very happy if some people are using local functions and others private methods for the same thing. Of course, the performance benefits are always welcome.

Literal Improvements


   In C# you can declare numbers as either decimal or hexadecimal literals. C# 7 adds binary literals with the 0b prefix. For example, 0b1100 is the value 12 in decimal. In addition, we can now split digits in a literal with _. In fact, we can split with any number of _. Therefore 1_000_000 is a valid literal but so is 1__0_00___0_00. The features are especially useful together. While we rarely put decimal values above 1 000 000 in our code when declaring literals as binary there are indeed a lot of digits that we should be able to count easily. I would dismiss binary literals as not important but in the last 2 years one of my side projects requires a lot of them. My life will improve significantly with this addition.

Ref Returns and Locals


   Ref returns are the mirror image of ref parameters. You can now return a ref from a method and store it in a ref variable. Here is an example I shamelessly stole from the official blog post

public ref int Find(int number, int[] numbers)
{
   for (int i = 0; i < numbers.Length; i++)
   {
       if (numbers[i] == number)
       {
           return ref numbers[i]; // return the storage location, not the value
       }
   }
   throw new IndexOutOfRangeException($"{nameof(number)} not found");
}

int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // aliases 7's place in the array
place = 9; // replaces 7 with 9 in the array
WriteLine(array[4]); // prints 9

In addition if we use the System.Runtime.CompilerServices.Unsafe class we can do very real pointer arithmetic with these ref values (please do not do it). I personally have never needed this feature but I imagine this is very important for people who require performance like game developers. This feature will make their code both simpler and faster.  The performance gains come from the removed need to copy values. This solution is also simpler the non-ref solution because if we were to write the code above without this feature we would have to pass around not only the value but also the internal index in the array and pass that index back to the modifying method. We would then need to validate this index and so on.

Generalized Async Types


   In C# 6.0 the async methods can only return void or Task. In C# 7.0 the compiler will allow any type that conforms to the async patter i.e. it has GetAwaiter method and the awaiter has the proper signature. Note that awaiting non-Task types was allowed in the past; it is just returning them from async methods that is new.  In addition to having an object with the proper shape you need to tell the compiler how to build your object which happens with a builder class. There is an AsyncMethodBuilderAttribute that you put on your Task-like type to tell the compiler where to find the builder.

   The useful example comes with the introduction of ValueTask<T> which is a value type that works as a Task. While it might be problematic to return a value type to be used as a Task there are cases where ValueTask works just fine and you can save an allocation. More specifically ValueTask should be used when the result is available like when we have the result cached or when we just return a value. Here is an example I stole

async ValueTask<int> TestValueTask(int d)
{
   await Task.Delay(d);
   return 10;
}

The value is already available and there is no need to create an object to wrap it. This would be of no benefit if the value was obtained through a real async code. If we use caching it is often the case that one path does a real async call but if the value is available in cache there is no async call. These cases can also reduce allocations by using ValueTask as their return type. Note that the default for async methods should still be Task and ValueTask should be used only when performance is needed and the improvement is verified.

More Expression Bodied Members


   C# 6.0 introduced the option to declare methods and get-only properties with lambda-like syntax. In C# 7.0 this ability is extended to other members like, constructors, destructors, getters and setters. Here is another stolen example

class Person
{
   private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
   private int id = GetId();

   public Person(string name) => names.TryAdd(id, name); // constructors
   ~Person() => names.TryRemove(id, out _);              // destructors

   public string Name
   {
       get => names[id];                                 // getters
       set => names[id] = value;                         // setters
   }
}

In my comment on C# 6.0 I said that I do not like expression bodied methods and properties and in fact as of today I have not used this feature in production code. However consistency is a good thing and it makes sense that other members can work in a way similar to properties and methods.

Throw expressions


   Before C# 7.0 throwing an exception was a statement. You could work around that by introducing a suitable method that simply throws an exception and using a call to that method as an expression. In C# 7.0 throw can be used as an expression and it seems like it can fill an expression of any type because it will not have to provide the value anyway. The example goes like this

public static bool IsAlphaNumeric(string s)
{
   s = s ?? throw new ArgumentNullException(s);

   //do somethign with s ...
}

Previously you would need to use a full blown if here. I personally prefer the full blown if but I see the value of throw expressions especially paired with other new and future features. For example they play well with the new expression bodied members. If your method definition is just an expression you need a way to throw an exception inside an expression. What is even more important is that they are needed for future additions like the match statement. And this brings us to the biggest C# 7.0 feature – pattern matching.

Pattern Matching


   Pattern Matching is C# 7.0 is honestly disappointing. Time concerns forced the team to split the feature into basic and advanced parts and obviously the first to hit production are the basic parts. Problem is basic parts are not enough so we will have to wait for C# 8.0 for the cool stuff. However lets first take a step back and discuss what pattern matching is.

  At its core pattern matching is quite simple concept. Perform a test for the type and shape of the object and if the result is true put the parts of the object into variables and execute some code. It is basically a shortcut for a bunch of ifs with type tests, casts and variable declarations. It turns out this simple concept is quite powerful and can be used to describe quite complex logic in a succinct and clear way. Let's take a look at the first expression which can work with patterns – the is expression.

object o = "foo";

if (o is string s)
{
   Console.WriteLine(s.Length); //prints 3
}

Console.WriteLine(s); //Compile-time error: Use of unassigned local variable s

This pattern can replace code snippets like these

object o = "foo";
string s = o as string;
if (s != null)
{
   Console.WriteLine(s.Length); //prints 3
}

object o = "foo";
if (o is string)
{
   string s = (string)o;
   Console.WriteLine(s);
}

The pattern matching solution is better because it only contains one cast and using the variable outside the if statement is a compile-time error. This particular example uses a type pattern which tests for a type but there are other patterns like the constant pattern.

object o = "foo";
if (o is "foo")
{
   Console.WriteLine(o); //prints foo but o still has a static type of object
}

There is also one more pattern – the var pattern which is currently useless. It just creates a new variable for the same object

object o = "foo";
if (o is var s) //s has a static type of object, we just created another variable for the same object
{
   Console.WriteLine(s);
}

Here is one interesting usage of pattern matching with is which would be very useful in JavaScript but does not happen often in C# code.

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

It should be obvious that the usefulness of these patterns with the is expression is quite limited. Let's see something more advanced – the switch statement.

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

In this (stolen) example we can see the type pattern used several times and the constant pattern used at the end (the one with null). Each case tests for a type and puts the value into a variable with the said type. In addition we can enhance a case with a when clause which is basically an if. The cases are now tested in order except the default case which is always last (please put it last when you write code) because of backward compatibility.

While these types of pattern matching do have some usefulness they are basically glorified casts. While technically they are pattern matching they are definitely not enough to say that C# now supports pattern matching in spirit. This is not what people who wanted pattern matching in the language imagined. Now the good news is that the pattern matching proposal was split in two and what we are getting is the basic part. The advanced part is still being worked on and will hopefully make it into C# 8.0. The funcitonality in C# 7.0 is something that advanced pattern matching still needs to step on. So what are some examples of advanced pattern matching?

- the match expression. The match expression is similar to the switch statement but it returns a value. This makes it much more useful because the developer will not have to store the values in variables. In addition the match expression must be exhaustive so you will get compile time errors if your patterns did not cover some case. Throw expressions are important in order to have useful match expression so that we can throw an exception from a case.

- tuple deconstruction. It is not currently supported in pattern matching.

- record types. Similar to tuples but with named elements. When these are introduced deconstructing them based on their elements will be important.

These are things that I have seen mentioned in proposals (here and here ) but pattern matching can go even further. In languages like Haskell you can look inside lists and decompose the list in variables in various ways. There are active patterns, recursive patterns and other things that I do not currently understand fully. The pattern matching mechanism can be very powerful and I hope that some day C# will have one that is powerful and not just type casting dressed as pattern matching.
Tags:   english programming 
Posted by:   Stilgar
16:28 06.04.2017

Comments:

First Previous 1 Next Last 

Posted by   ___XXX_X_XXX___ (Unregistered)   on   22:01 27.04.2017

Абе тоя C# 7.0 с тези Tuple-та и local functions много почнал да заприличва на JavaScript ;) Anders що не ни обърне повечко внимание на TypeScript-а ами ми се занимава с някакви си козметични промени по C# :)

Posted by   Stilgar   on   00:02 28.04.2017

Написал си F# грешно :)

First Previous 1 Next Last 


Post as:



Post a comment: