Monday, May 8, 2017
New features in C# 7.0
In this blog post, you will learn about new features in C# 7.0 which will make coding easier, and the code itself more readable and maintainable. The biggest changes are related to tuple and pattern matching functionality. Also, there is a possibility of declaring local functions within an existing function, as well as a deconstruction of types into smaller parts and assigning their values to new variables. Digit separators and binary literals are introduced to make the code more transparent.
Pattern matching
Pattern matching is a new feature in C# 7.0 which is not yet complete but will be in the forthcoming versions. There are two ways of pattern matching, with is
expression and switch
statement. The class below is used in two examples to follow.
public class Foo
{
public string A { get; set; }
}
public class Bar : Foo
{
public int B { get; set; }
public Bar(string a, int b)
{
this.A = a;
this.B = b;
}
}
public class Baz : Foo
{
public bool C { get; set; }
public Baz(string a, bool c)
{
this.A = a;
this.C = c;
}
}
Is-expression pattern
In the code below, the is
expression pattern checks if the object matches with the given type, and adds variable to the right side of the expression, so the result is cast into that variable.
Foo foo = new Foo();
Bar bar = new Bar("bar", 1);
Baz baz = new Baz("baz", true);
if (bar is Foo d)
Console.WriteLine($"Bar is {d.A}");
else Console.WriteLine($"Bar is not Foo");
//OUT: Bar is bar
Switch statement pattern
The switch case
statement in C# 7.0 is enhanced so it can be used on any type of variable (not only on primitive ones ). Case clauses can contain a pattern and can have additional conditions on them, as shown in the example below.
switch (baz)
{
case Foo foo1 when (foo1.A == "bar"):
Console.WriteLine($"Foo.A = {foo1.A}");
break;
case Baz baz2 when (baz2.A == "baz"):
Console.WriteLine($"Baz.C = {baz2.C}");
break;
default:
Console.WriteLine("Not found");
break;
}
//OUT: Baz.C = True
Tuples
Tuples already exist in C# since .NET Framework 4.0, but C# 7.0 enhances the functionality and code readability by adding tuple types and tuple literals. In the example below the method can, at once, return multiple variables all wrapped up as elements in a tuple value. Those elements can be accessed individually.
public (string, string, long) TupleReturn(bool qux)
{
string foo = string.Empty;
string bar = string.Empty;
long baz = 0;
if (qux)
{
foo = "foo";
bar = "bar";
baz = 3;
}
return (foo, bar, baz);
}
TupleExample te = new TupleExample();
var foobar = te.TupleReturn(true);
Console.WriteLine($"Tuple returned values: foo = {foobar.Item1}, bar = {foobar.Item2}, baz = {foobar.Item3} ");
//OUTPUT: Tuple returned values: foo = foo, bar = bar, baz = 3
Default names are Item1
, Item2
, etc., but new ones can be assigned to tuple types as shown in the code below:
public (string foo, string bar, long baz) TupleReturn(bool qux)
...
Console.WriteLine($"Tuple returned values: foo = {foobar.foo}, bar = {foobar.bar}, baz = {foobar.baz} ");
//OUTPUT: Tuple returned values: foo = foo, bar = bar, baz = 3
Local functions
This feature allows defining and calling local methods within the other method. The local method can be defined in any scope, and it will be available in that scope and all inner scopes, same as the local variable. Here is an example of three different ways that can be used to define the local function:
static void Main(string[] args)
{
int Foo(int foo)
{
return 100 * foo;
}
Console.WriteLine(Foo(5));
//OUT: 500
int Bar(int bar) => 100 * bar;
Console.WriteLine(Bar(5));
//OUT: 500
int qux = 100;
int Baz(int baz)
{
return qux * baz;
}
Console.WriteLine(Baz(5));
//OUT: 500
}
Deconstruction
Deconstruction is a process used for splitting the variable value into parts and storing them into new variables. Deconstruction can be easily used and demonstrated on tuples like this:
TupleExample te = new TupleExample();
(string foo, string bar, long baz) = te.TupleReturn(true);
Console.WriteLine($"Tuple returned values: foo = {foo}, bar = {bar}, baz = {baz} ");
//OUTPUT: Tuple returned values: foo = foo, bar = bar, baz = 3
Deconstruction can be done in a few different ways:
- using
var
keyword for the individual variables declaration
(var foo, var bar, var baz) = te.TupleReturn(true);
- placing a single
var
keyword outside of the parentheses as an abbreviation
var (foo, bar, baz) = te.TupleReturn(true);
- using existing variables with a deconstructing assignment
string foo = string.Empty;
string bar = string.Empty;
long baz = 0;
(foo, bar, baz) = te.TupleReturn(true);
Literal improvements
#### Digit separator
We can now use a digit separator "_"
inside the number literals. In the older versions of C# when there was a number, e.g. 1000000000000000, it was hard to count how many zeroes it had. Now, with a digit separator "_"
, this number can be separated in any way that will make it more transparent. Here is an example:
var b = 1_000_000_000_000_000;
var c = 1_00000_00000_00000;
Console.WriteLine(b);
Console.WriteLine(c);
//OUT: 1000000000000000
//OUT: 1000000000000000
Obviously, the value of the literal is unaffected by the digit separator "_"
. It is here just to improve the readability.
Binary Literal
C# has already supported hexadecimal literals by using 0x
, but in C# 7.0 there are binary literals available as well. The digit separator "_"
can be used inside the binary literals. In the code below, bit patterns are directly specified using 0b
instead of having to know hexadecimal notation by heart.
var b = 0b10010101;
var c = 0b1100_1110_0111;
Console.WriteLine(b);
Console.WriteLine(c);
//OUT: 149
//OUT: 3303
Conclusion
C# 7.0 brings a lot of new features that will be useful for coding. Features such as pattern matching and local functions could change how a lot of everyday common code is written. Not all features are covered in this post, as some of them are still not available in Preview - out variables, ref returns, generalized async return types, etc. For more information, please visit MSDN blog.