What’s new in C# 7.0

With the release of Visual Studio 2017, C# 7.0 saw the light. By downloading the community edition of VS 2017, you can use the new .Net version right away. In this post, I will walk through the new language features, which mainly focus on data manipulation.

Out variables

Before C# 7.0, you had to declare out variables before using them. By C# 7.0, you don’t need that anymore and can declare them within the declaration of the method. Before C# 7.0 you had to do something like this:

public class CSharpSeven
{
 public void OutVariables(out i)
 {
  i = 10;
 }
}

CSharpSeven csharpSeven = new CSharpSeven();

int i = 0;
csharpSeven.OutVariables(out i);
Console.WriteLine(Convert.ToString(i));

So, before passing the variable i, you have to initialize it. C# 7.0 now allows us to skip the declaration of i. It will be done by the method declaration:

public class CSharpSeven
{
 public void OutVariables(out int i)
 {
  i = 10;
 }
}

CSharpSeven csharpSeven = new CSharpSeven(); csharpSeven.OutVariables(out int i);

So the main advantage in 7.0 is that we don’t have to declare an out variable before using it. It is also allowed to use a var instead of a specified type, since the compiler can determine the type of the requested variable.

Binary literals and digit separators

You can now specify a bit pattern direct, without having to convert it by hand to hexadecimal and write it down. Before 7.0 we would use:

var byte = 0xA;

Now we can use:

var byte = 0b1010

Digit separators, alow you to separate digits within number literals. They don’t have any effect on the value. So we can write

var byte - 0b1_010

or

var byte = ob10_10

and so on. Even multiple underscores after each other are allowed:

var byte = 0b10_____10

Local functions

Sometimes you have a helper method that is used only by one other method. In this case you can define the helper method as a local function. The function is then only known within the method you declare this sub-method.

The following example shows a local method called printChar, which will print a char on the console. We feed this local method by looping through a string of characters.

public static void DoSomething()
{
 void printChar(char a)
 {
   Console.WriteLine(a);
 }

 string text = "An example";
 for (int i = 0; i < text.Length; i++)
 {
   printChar(text[i]);
 }
}

The local method is known within the whole scope of the main method. So, the following is also allowed:

public static void DoSomething()
{
 string text = "An example";
 for (int i = 0; i < text.Length; i++)
 {
 printChar(text[i]);
 }

 void printChar(char a)
 {
  Console.WriteLine(a);
 }
}

Here I declared the local method printChar after where I use it and still I’m allowed to use printChar within the for-loop.

Tuples

Tuples allow us to group data on the fly, without defining a special type by ourself. Take for example a method which will return a point. Before C# 7.0 we would have something like this:

public class Point 
{
  public int X;
  public int Y;
}

public class ClassUsingPoint
{
  private Point aPoint;

  public Point GetPoint
  { return aPoint; }
}

With the new tuple functionality, we can now skip the Point class and rewrite the class ClassUsingPoint like this:

public class ClassUsingPoint
{
  private (int X, int Y) aPoint;
  public (int X, int Y) GetPoint 
  { return aPoint; }
}

So by using (int X, int Y) we define our Point class in-directly.

Discards

When using the tuples, which we discussed previously, you’re not always interested in all of the parameters. In our example of the Point type, we only want to use the y-value. In this case we can write:

var pointClass = new ClassUsingPoint();
var (_,y) = pointClass.GetPoint();

A discard is a write-only variable and you can use it by the underscore character ‘_’.

Discards are supported in the following cases:
– Deconstructing tuples or other user-defined types;
– When calling methods with out-parameters;
– In a pattern matching operation with the is and switch statements;
– As a standalone identifier when you want to explicitly identify the value of an assignment as a discard.

Pattern matching

Pattern matching is within C# 7.0 supported only for the following two constructs: is expressions and case clauses. In newer versions of C#, there will be extended support for pattern matching.

Pattern matching comes in use when using derived types. In that case you often have to do some type checking in order to process the derived type. Let’s say we have the following abstract class Human with its derived classes Man and Woman:

public abstract class Human
{
 public string Name { get; set; }
}

public class Man : Human
{
 public Woman Wife { get; set; }
}

public class Woman : Human
{
 public Man Husband { get; set; }
}

‘is’ expressions

We can use now the is operator to check the type of variable w:

Woman w = new Woman();

if(w is Human)
{
Console.WriteLine("Is a human");
}
if (w is Woman)
{
 Console.WriteLine("Is a woman");
}
if (w is Man)
{
 Console.WriteLine("Is a man");
}

Case clauses

Now within case-statements this a type switch can be used like this:

public void ProcessHuman(Human h)
{
  switch (h)
  {
    case Man m:
       // Do something
       break;
    case Woman w:
      // Do something
      break;
    case null:
      // Do something
      break;
    default:
      break;
  }
}

Null instances must be handled separately, or can be handled by the default case.

Throw exceptions

Since throw was always a statement, you couldn’t use it everywhere. For example you couldn’t use it in conditional expressions, null coalescing expressions and some lambda expressions.

With the null-coalescing operator ?? the left-hand operand will be returned if the operant is not null, otherwise it returns the right hand operant. Within null coalescing expressions you can now use this statement :

string text = null;
Console.WriteLine(text ?? throw new Exception("Text is null!"));

So the exception will be thrown if text is null. In older versions you had to check for null with an if-statement and then throw the exception.

Generalized async return types

Task and Task<Result> used with async are reference types, which can affect the performance of your application. C# 7 introduces now a new generalized return type System.Threading.Tasks.ValueTask, which is more lightweight type instead of a reference type. To use this new type you have to add the System.Threading.Tasks.Extensions NuGet package.

Ref locals and ref returns

Also new is that besides parsing an argument as a reference, you can also now return reference via a method or define local references. This way you have a C++ style way of returning a pointer to a value type, instead of using larger constructions. See the following example:

public class ReferenceExample
{
    private int[] m_array = { 1, 2, 3, 4, 5 };

    public ref int GetNumber(int index)
    {
        return ref m_array[index];
    }

    public void PrintArray()
    {
        for (int i = 0; i < m_array.Length; i++)
        {
            Console.WriteLine(m_array[i]);
        }
    }
}

The method GetNumber will retrieve now a reference to the index of the array. In this way, it is possible to change the value of the array, defined within ReferenceExample. The following example illustrates this:

var example = new ReferenceExample();
example.PrintArray();

ref int number = ref example.GetNumber(2);
number = 20;

example.PrintArray();

The output of this example will be:

1
2
3
4
5

1
2
20
4
5

So you see, we changed via the reference the value of the 3rd (index 2) number of the array into 20. Without references, you would get twice the same numbers “1, 2, 3, 4, 5”, since an int is a value type.

Note that ref int number is our local reference.

Posted in C# by Bruno at November 29th, 2017.

Leave a Reply