Experimenting With New Features Of C# 7

Last year, I worked out and actually wrote about C# 6 features, in a way that it would be easy for you to understand how they were actually created, so that we can easily get a good understanding of C# language, as it is evolving. In this post, I will be talking about the new improvements in C# 7, what they are, how they are implemented and what you should understand before you start using them in your programs. I will also sum up the post by including my suggestions and thoughts as to which of these features are helpful and which of them are not.

I will be going through a bit of IL code and LINQPad will be used for this, so if you understand what .NET IL code is, it would be a good plus point for you, if not, then it doesn’t matter, as I will explain the concept myself.

Important features to cover

C# 7 introduces several improvements to the language and ignoring .NET 4.7, there is still a lot of stuff to cover, but I will not be covering any of that. I want to cover only a few topics because it is already too much for one post. Anyway, the list given below shows the important features of C# 7, which I want to experiment with

  1. Literals.
  2. Local functions.
  3. Tuples improvements in C# 7.
  4. Pattern matching cases.
  5. Async improvement — ValueType Task<T> object.
  6. Deconstruction.

There are other some basic differences there as well, but most of them are improvement or additions to things which we had already looked in C# 6, such as the expression bodied methods and members, so I will not cover them here at all. However, I might be covering an entire C# 7 improvement course in a separate ebook for the developers, so stay tuned. 

How to use C# 7?

Some might say, just download Visual Studio 2017 Community edition and you will get everything of C# 7, by default but there are some who cannot access these tools, updated IDEs, SDKs because of several reasons. Thus, to sum up the post for them, I will give an overview of the ways with which you can use C# 7 in older versions of .NET framework and with the older versions of Visual Studio.

  1. Most of the features are packages; Tuple feature is an extension package from NuGet, which you can install.
  2. Most of the features are syntactic sugars, which are just like the string interpolation, so you will still be able to use maximum features of C# 7 in older .NET framework.

Visual Studio 2017 Community edition doesn’t charge anything and has some benefits packed, so consider it. 

Understanding the improvements

Now, let us consider understanding the improvements one by one and see how Microsoft (or the community) implemented them in the language. Review the list given above. I will be covering only the topics provided there, so you know which part contains which of the sections here and I will be providing some more tips in the article.

Literals

The first and foremost thing in C# 7 is that most of the people are still trying the literals. Most of the times the improvement has nothing to do with more readability. I would not talk much more on this topic, instead would only provide my suggestions on the topic and then close it. 

  1. // At the moment C# supports  
  2. var dead = 0xDEAD;  
  3. var beef = 0xBEEF;   

Now, the current literals support the “_” character to separate the value itself, but what is the used of it?

var dead = 0xD_EAD; // ? Did I mean to write DREAD? Nope.

// So, not much useful for me.

Second, let’s try it out on a numeric value.

var kms = 10_000;

The way I see these values, what I feel about them is that they are like a blank field or a template. Instead of this, what I would have personally loved to see in C# would have been the locale based separators for the digits.

var kms = 10,000; // Looks more natural.

Look, the way C# is providing this feature, is that this is just syntactic sugar. Thus, it is provided by the IDE, why not use the current locale, and map the digits from there to the native types.

  1. The comma as a separator is more natural in many ways. Even, 0xAD,BC, would be more readable than 0xAD_BC. This seems as if I actually skipped a value there.
  2. Visual Studio should be intelligent enough to actually integrate this feature with the current locale settings of the user.

However, the problem with my idea is that it is based on the locale of the user and not the standard. In the cases where there might be teams working on the same project, there might be a mismatch breaking the entire build, which is because of a literal.

Local functions

The thing about local functions refers to any other function, which is defined. They generate the same IL code and take the same amount of time and the only thing is that they belong to the function itself, unlike an object instance or a class type (as in static functions). Thus, let’s have a look at what local functions are in real.

Let us assume that I have a function, which processes the list of integers and then returns something. 

  1. int Process() {  
  2.     var list = new List<int>();  
  3.     var sum = Sum(list);  
  4.    
  5.      int Sum(List<int> items) {  
  6.          return items.Sum();  
  7.      }  
  8.      return sum;  
  9. }   

At the first glance, this looks just like any other ordinary C# program. You provide the variables, set some functions and then return it; print it. However, this is just a sample function, which has state, a function and something to return to the caller. The function is not different than the code given below. 

  1. int Process() {  
  2.      var list = new List<int>();  
  3.      var sum = Sum(list);  
  4.    
  5.      return sum;  
  6. }  
  7.   
  8. // Notice, that both are instance functions.  
  9. int Sum(List<int> items) {  
  10.      return items.Sum();  
  11. }   

Thus, what happens at the background is also similar. The IL generated by these methods is same.

IL_0000: nop

IL_0001: ret

Process

IL_0000: nop

IL_0001: newobj System.Collections.Generic.List<System.Int32>..ctor

IL_0006: stloc.0 // list

IL_0007: ldloc.0 // list

IL_0008: call UserQuery.<Process>g__Sum1_0

IL_000D: stloc.1 // sum

IL_000E: nop

IL_000F: ldloc.1 // sum

IL_0010: stloc.2

IL_0011: br.s IL_0013

IL_0013: ldloc.2

IL_0014: ret

<Process>g__Sum1_0

IL_0000: nop

IL_0001: ldarg.0

IL_0002: call System.Linq.Enumerable.Sum

IL_0007: stloc.0

IL_0008: br.s IL_000A

IL_000A: ldloc.0

IL_000B: ret

The only difference in the second case is the label used for the function block and the call operation has the function name, instead of the g__Sum1_0. Hence, the difference is only the naming of these functions, else they have common things given below.

  1. They are instance functions.
  2. They perform same operations, generate the same IL code as well.
  3. They can be used to wrap any task, which requires to be executed, as needed.

While this is shown, there are few differences to note here as wel.,

  1. The local functions have an access to the function local variables as well.
  2. For the external functions, you must manually pass the parameters or at least use ref, out parameters.
  3. Local functions have direct connection to the variables of a function.

The location of the function inside the function doesn’t matter. It is only the matter of taste and C# would then generate the IL for it, so that the compiler knows where the function is. What I mean to say here is, you can actually return a value before even writing the local function , C# would compile the code and make it work properly as well. 

  1. int Process() {  
  2.     var list = new List<int>();  
  3.     var sum = Sum(list);  
  4.    
  5.     return sum;  
  6.      
  7.     // Function here.  
  8.     int Sum(List<int> items) {  
  9.         return items.Sum();  
  10.     }  
  11. }   

The IL code for this is given below.

Process

IL_0000: nop

IL_0001: newobj System.Collections.Generic.List<System.Int32>..ctor

IL_0006: stloc.0 // list

IL_0007: ldloc.0 // list

IL_0008: call UserQuery.<Process>g__Sum1_0

IL_000D: stloc.1 // sum

IL_000E: ldloc.1 // sum

IL_000F: stloc.2

IL_0010: br.s IL_0012

IL_0012: ldloc.2

IL_0013: ret

<Process>g__Sum1_0

IL_0000: nop

IL_0001: ldarg.0

IL_0002: call System.Linq.Enumerable.Sum

IL_0007: stloc.0

IL_0008: br.s IL_000A

IL_000A: ldloc.0

IL_000B: ret

Typically, the only difference is if you write the return val at the end, there will be a nop bytecode operation added to the IL, which as you may know is a no operation command.

Local functions are instance functions

One other difference is that, you can easily create static functions, but however, the local functions cannot be static functions. Even if the parent function is a static function, the local function cannot be a static one. I don’t actually understand this one thing, why? but perhaps, the context itself is static and the function would ultimately end up being static or just say, it works.  

  1. static int Process() {  
  2.     var list = new List<int>();  
  3.     var sum = Sum(list);  
  4.    
  5.     return sum;  
  6.   
  7.     // Function here.  
  8.     static int Sum(List<int> items) { // CS0106: The modifier "static" is not valid.  
  9.         return items.Sum();  
  10.     }  
  11. }   

In most cases, the local functions seems to be helpful, but in major cases this might be an extra region to cover.

Local functions as lambdas

One final thing about local functions is that they can be easily created as lambdas. Thus, the code is given below. 

  1. int Process() {  
  2.     var list = new List<int>();  
  3.     var sum = Sum(list);  
  4.   
  5.     return sum;  
  6.   
  7.     // Function here.  
  8.     int Sum(List<int> items) {  
  9.         return items.Sum();  
  10.     }  
  11. }   

It can be easily rewritten. 

  1. int Process() {  
  2.     var list = new List<int>();  
  3.     var sum = Sum(list);  
  4.    
  5.     return sum;  
  6.   
  7.     // Function here.  
  8.     int Sum(List<int> items) => items.Sum();  
  9. }   

The change has a huge performance improvement over the older (function) way. The lambdas are definitely stronger. As I move further in the article, you will see how many concepts are taken from the Function programming and Tuples are one of them.

Tuple improvements in C# 7

As you may know, tuple is a concept of functional programming, and there are very powerful and useful types; C# guys don’t know much about it, but the function world such as Haskell have been using them for a while. Previously, System.Tuple type was used to create the tuples. Before, I go down, let me tell you that a tuple doesn’t only mean that you can return more than 1 value from a function. This is just one use, where a tuple proves to be useful and not all of it. Remember, that I said it comes from functional world. In the functional world, there are no objects and thus no classes and no instances. Thus, when you store something, you would typically be using a tuple, which would show a record or an entity and not an object.

Tuples have their own benefits and objects have their own. In the older versions, they were created as a type of tuple, a structure. However, improvements have come and they are not a part of C# language syntax, so you do not have to write anything extra and you still get the benefit of it. Also, the tuple is now a System.ValueTuple instead of the System.Tuple.

Now, let us see what they are and if are they useful or not.  

  1. void Main()  
  2. {  
  3.     var person = GetPerson();  
  4.    
  5.     Console.WriteLine($"{person.Item1} is {person.Item2} years old.");  
  6. }  
  7.   
  8. (string, int) GetPerson() {  
  9.     return ("Afzaal Ahmad Zeeshan", 21);  
  10. }  
  11.   
  12. // Output  
  13. // Afzaal Ahmad Zeeshan is 21 years old.   

In the code given above, I am creating a separate function. A function that returns a tuple type; a tuple is wrapped inside the parenthesis, and has a type for each element, the name of the elements is conditional, which defaults to ItemN.

Similarly, the code gets the value from the function and prints it on the console. Studing the IL code for this would give a more in-depth overview of the tuple type.

IL_0000: nop

IL_0001: ldarg.0

IL_0002: call UserQuery.GetPerson

IL_0007: stloc.0 // person

IL_0008: ldstr "{0} is {1} years old."

IL_000D: ldloc.0 // person

IL_000E: ldfld System.ValueTuple<System.String,System.Int32>.Item1

IL_0013: ldloc.0 // person

IL_0014: ldfld System.ValueTuple<System.String,System.Int32>.Item2

IL_0019: box System.Int32

IL_001E: call System.String.Format

IL_0023: call System.Console.WriteLine

IL_0028: nop

IL_0029: ret

GetPerson

IL_0000: nop

IL_0001: ldstr "Afzaal Ahmad Zeeshan"

IL_0006: ldc.i4.s 15

IL_0008: newobj System.ValueTuple<System.String,System.Int32>..ctor

IL_000D: stloc.0

IL_000E: br.s IL_0010

IL_0010: ldloc.0

IL_0011: ret

If you try to pay attention, you will see that all it does is, it gets the value and prints it as soon as the value is loaded onto the execution stack. However, I always thought perhaps, tuples were never needed and that the current types were enough to be worked around with, but if you try to unwrap the values and do something on them, you will have a drastic change in the performance.

var (name, age) = GetPerson();

Console.WriteLine($"{name} is {age} years old.");

This code is bit more readable for the people, but has an extra overhead for the program because now the program must also push the variables on the stack.

  1. It loads the tuple type from the function.
  2. Maps the types to the named variable.
  3. Notice that the name and age variables are of different type, but they are both sharing the var type.
  4. It continues doing the same work that it was.

The default tuple ItemN type has a less readability but it gives you an edge, if you need it.

As for the IL, here is the IL for the code given above.

IL_0000: nop

IL_0001: ldarg.0

IL_0002: call UserQuery.GetPerson

IL_0007: dup

IL_0008: ldfld System.ValueTuple<System.String,System.Int32>.Item1

IL_000D: stloc.2

IL_000E: ldfld System.ValueTuple<System.String,System.Int32>.Item2

IL_0013: stloc.3

IL_0014: ldloc.2

IL_0015: stloc.0 // name

IL_0016: ldloc.3

IL_0017: stloc.1 // age

IL_0018: ldstr "{0} is {1} years old."

IL_001D: ldloc.0 // name

IL_001E: ldloc.1 // age

IL_001F: box System.Int32

IL_0024: call System.String.Format

IL_0029: call System.Console.WriteLine

IL_002E: nop

IL_002F: ret

IL shows that there is no need for duplication of the data, no need for extra stack pushing, plain processing is going down the road.

No limit on the length

One more thing to realize, here is that the older types have a limit on the types, wh,ich you can use such as, a 7-tuple type is Tuple<T1, T2, T3, T4, T5, T6, T7>, but however there is no limit on the length of the tuple typing in this case. 

  1. (string, string, string, string, string, string, string, string, string, string, string) GetPerson() {  
  2.     return (nullnullnullnullnullnullnullnullnullnullnull);  
  3. }   

If your code requires a type, which has around 11 elements, you can use this without any problem at all.

Finally, there is no limit on the type of parameter to be used. You can use the local variables in the tuple. However, once again, the naming of the tuple elements is just the personal sort of taste, which you can or cannot prefer, so I won’t talk about it any further.

Also, using a custom value typed structure also has some overheads, so if you want to create a separate object of this type, it is also not a good option. In most cases, I am going to enjoy, using the tuple types in C#.

Pattern matching cases

Pattern matching is the use of current structures in C# to initialize the variables and check the conditions in one place. For example, have a look at the code given below. 

  1. void Main()  
  2. {  
  3.     var types = new object[] { "Afzaal", 21, 4.5d };  
  4.     Process(types);  
  5. }  
  6.   
  7. void Process(object[] list) {  
  8.     foreach (var item in list) {  
  9.         if(item is String) {  
  10.             Console.WriteLine($"{(string) item} is String type.");  
  11.         } else if(item is int) {  
  12.             Console.WriteLine($"{(int) item} is int type.");  
  13.         } else if(item is double) {  
  14.             Console.WriteLine($"{(double) item} is double type.");  
  15.         }  
  16.     }  
  17. }  
  18.   
  19. // Output  
  20. // Afzaal is String type.  
  21. // 21 is int type.  
  22. // 4.5 is double type.   

This code has a condition, which checks, if the type is matching and afterwards casts it to the proper type to show the results, whereas, if we use C# 7 way to do this, we would get the results given below. 

  1. void Main()  
  2. {  
  3.     var types = new object[] { "Afzaal", 21, 4.5d };  
  4.     Process(types);  
  5. }  
  6.   
  7. void Process(object[] list) {  
  8.     foreach (var item in list) {  
  9.         if(item is String str) {  
  10.             Console.WriteLine($"{str} is String type.");  
  11.         } else if(item is int i) {  
  12.             Console.WriteLine($"{i} is int type.");  
  13.         } else if(item is double d) {  
  14.            Console.WriteLine($"{d} is double type.");  
  15.         }  
  16.     }  
  17. }   

This code has somewhat better readability, as compared to the old one and saves us from an extra cast, because that is taken care of by the IL in the background, thus making it easier for us to write the code, the same is the case for the switch statements, as example, let’s see how the integer check works in this case.

IL_0036: ldloc.2 // item

IL_0037: isinst System.Nullable<System.Int32>

IL_003C: unbox.any System.Nullable<System.Int32>

IL_0041: stloc.s 07

IL_0043: ldloca.s 07

IL_0045: call System.Nullable<System.Int32>.GetValueOrDefault

IL_004A: stloc.s 05 // i

IL_004C: ldloca.s 07

IL_004E: call System.Nullable<System.Int32>.get_HasValue

IL_0053: stloc.s 06

IL_0055: ldloc.s 06

IL_0057: brfalse.s IL_0074

IL_0059: nop

IL_005A: ldstr "{0} is int type."

IL_005F: ldloc.s 05 // i

IL_0061: box System.Int32

IL_0066: call System.String.Format

IL_006B: call System.Console.WriteLine

IL_0070: nop

Here, it captures the value from the list, checks, if it is the instance of the Nullable type of the object. It gets the value by unboxing it and then the rest of the stuff is the same process or formatting the string and writing it. 

As for the switch statements, the syntax is given below. 

  1. switch (obj) {  
  2.     case Person p:  
  3.          break;  
  4.     case Shape s:  
  5.          break;  
  6.     default:  
  7.          break;  
  8. }   

The improvement is, instead of having a primitive type here, you can now match the object type here, which is the same as I have already shown, using the example of if…else block. Also, you can use the when block to make sure that a case gets evaluated only if the condition is met, which provides you a good way of range based switch statement. 

  1. void Process(object[] list) {  
  2.     foreach (var item in list) {  
  3.         switch(item) {  
  4.             case String str:  
  5.                 Console.WriteLine($"{str} is of String type.");  
  6.                 break;  
  7.             case int i:  
  8.                 Console.WriteLine($"{i} is of int type.");  
  9.                 break;  
  10.             case double d:  
  11.                 Console.WriteLine($"{d} is of double type.");  
  12.                 break;  
  13.         }  
  14.     }  
  15. }   

This gives the same output and works like a charm in the case. Also, if you like, you can add a condition to evaluate an integer value or any value based on a condition. Personally, I loved this feature because there were cases where one of the structure was unable to be used and one was not efficient. However, these improvements are bridging the gap.

Async improvement — ValueType Task<T> object

To most, this might not come as a surprise, but to those who do some hardcore multithreaded programming and rely on this, for their job, the new improvement is just amazing. This means that can now easily use the ValueTask. This type actually exists in the System.Threading.Tasks.ValueTask, but comes from an extension library from NuGet; System.Threading.Tasks.Extensions. You can easily download it for your own project and get started, using the new type.

However, there are few things to note

  1. This is a value type, instead of a reference type.
  2. The major reason is that a reference type, as the name suggests is required to be instantiated and an object is created.
  3. However, the value type, if is known would only require a stack push.

Even the documentation suggests that you should consider, using the Task<T> type, instead of the ValueTask<T> type. If you are aware that there is a performance improvement, then do it, else keep using the Task type.

I believe Microsoft is working on bringing some improvements to the package and will make this even better. Until then, let’s talk about the next topic in C# 7.

Deconstruction

This is a very interesting topic in C# 7, but I left this one for the last, so that I can easily explain the core concepts, which are required such as the Tuple types and some other extra features in C# 7 such as ref and out parameters.

The deconstruction and destructors are two different things and must never be intertwined under any circumstances. The deconstruction is a language feature, which provided a function, which converts the runtime object to a tuple. See, how important the concept of tuples was before this section.

The function itself is, given below.

  1. void Deconstruct(out string param1, out int param2) {  
  2.     // Set the out parameters here.  
  3. }   

Without this function, the language won’t be able to provide you with the deconstruct feature and you would typically have to write a personal function, which does it. If we create the function and subsequently execute the following, it works. 

  1. void Main()  
  2. {  
  3.     var person = new Person() { Name = "Afzaal Ahmad Zeeshan", Age = 21 };  
  4.    
  5.     var (n, a) = person;  
  6.     Console.WriteLine($"{n} is {a} years old.");  
  7. }  
  8.   
  9. public class Person {  
  10.     public string Name { get; set; }  
  11.     public int Age { get; set; }  
  12.    
  13.     public void Deconstruct(out string n, out int a) {  
  14.         n = this.Name;  
  15.         a = this.Age;  
  16.     }  
  17. }   

Removing the function causes an error, which tells the programmer that only a type that has the Deconstruct function can undergo a deconstruction, else add some helper functions etc. If none, then you cannot use it in your own code.

The properties of the objects that get returned are depending on what you want to create with the object. Also, you would require to use the tuple to store the values; thus, understanding how they works, is a good start for you.

Final Words

Although C# 7 has some great improvements, be it a syntax improvement, or the language improvement. I am impressed by some major changes, but I was annoyed by a few changes that were never required, or should have been left to the packages were supplied built it.

Most of the features of C# 7 are package based and thus they require a package to be installed from NuGet libraries whereas few of the improvements were installed natively and literal is one of them. The changes that I might have enjoyed were skipped — tuples, value task etc. They would have been added to the language.

Also, to the reader, I did not cover most of the improvements, because most of them were already covered in the previous post of mine and were similar and I eft them. However, this post was meant to give you a brief overview of the improvements in C# 7. I hope, you enjoyed reading the improvements, which are magic tricks. 

Stay tuned.


Recommended Free Ebook
Similar Articles