Last year, I worked out and actually wrote about C# 6’s features, in a way that it would be easy to understand how they were actually created so that we can easily get a good understanding of the C# language, as it is evolving. Since quite a while, what happens in the language is that, where everyone else is trying to optimize the background code, the code that gets generated, what happens in C# context is that the language gets optimized.
- I was studying about async/await these days, what I figured out was the feature itself was a syntactical sugar around the older ways of asynchronous programming. Only thing improved was that you were never allowed to play around in the sand of memory.
- Last year, when I was writing that post for C# 6, I figured that most of the features were also improvements over syntax, to ensure language evolution, yet keeping things on ground —
string
interpolation for example.
- While these changes can improve the way programs are executed, it still does not prevent any idiot from writing bad programs, which I am going to do in this post.
So, to keep things short: In this post, I will be talking about the new improvements in C# 7, and what they are, how they get implemented and what you should understand before you actually 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 have an understanding of what the .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 the .NET 4,7, there is still a lot of stuff to cover, but I will not be covering any of that stuff. I want to cover only a few of the topics because the stuff is already way too much for one post. Anyways, the following list shows the important features of C# 7 that I want to experiment with:
- Literals
- Local functions
- Tuples improvements in C# 7
- Pattern matching cases
- Async improvement —
ValueType
Task<T>
object
- Deconstruction
There are other some basic differences there as well, but most of them are improvement or additions to the stuff we already looked at 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 the entire C# 7 improvement course in a separate ebook for developers, keep a watch out for it.
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. Fine, 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 in which you can actually use C# 7 in older versions of .NET Framework, with older versions of Visual Studio.
- Most of the features are packages; Tuples feature is an extension package from NuGet, that you can install.
- Most of the features are syntactic sugars — just like the
string
interpolation — so you will still be able to use most of the features of C# 7 in older .NET Framework.
But, Visual Studio 2017 Community edition doesn’t charge anything, and has some benefits packed; so do consider it.
Understanding the Improvements
Ok, so now let us consider understanding the improvements one by one and see how Microsoft (or the community) actually implemented them in the language. Review the list above, I will be covering only the topics provided there, so you know which part contains which of the sections here, and will be providing some more tips, how-to and why-to in the sections themselves.
Literals
The first and foremost thing in C# 7, that most of the people are still trying out are the literals. Fair enough, most of the times, the improvement this has, 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.
var dead = 0xDEAD;
var beef = 0xBEEF;
Now, the current literals, support the “_
” character to separate the value itself, but of what use would the following be?
var dead = 0xD_EAD;
Secondly, 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, 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;
Look, the way C# is providing this feature is that this is just syntactic sugar. So, since this is provided by the IDE, why not use the current locale, and map the digits from there to the native types.
- The “comma” as a separator is more natural in many ways. Even
0xAD,BC
, would be more readable than 0xAD_BC
. That seems as if I actually skipped a value there.
- I agree that to some
0xAD,BC
, might not be as much visible; but given that the “comma” is used as a separator in most contexts, it can be used as a digit separator as well — provided the context of its usage.
- Visual Studio should be intelligent enough to actually integrate this feature with the current locale settings of user.
However, the problem with my idea is that it is based on the locale of the user, and not the standard, so in the cases where there might be teams working on the same project, there might be a mismatch breaking the entire build just because of a literal.
Local Functions
The thing about local functions is, somewhat interesting to me. They are just the way any other function is defined, they generate the same IL code and take the same amount of time, the only thing is that they belong to the function itself, unlike an object instance or a class type (as in static
functions). So, let’s have a look at what local functions are in real.
Let us assume that I have a function that processes the list of integers, and then returns something,
int Process() {
var list = new List<int>();
var sum = Sum(list);
int Sum(List<int> items) {
return items.Sum();
}
return sum;
}
At 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, in no way is different than the following code:
int Process() {
var list = new List<int>();
var sum = Sum(list);
return sum;
}
int Sum(List<int> items) {
return items.Sum();
}
Thus, what happens at the background is also similar, the IL generated by these methods is the 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
IL_0007: ldloc.0
IL_0008: call UserQuery.<Process>g__Sum1_0
IL_000D: stloc.1
IL_000E: nop
IL_000F: ldloc.1
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
. So, the difference in only the naming of these functions, otherwise they have common things:
- They are instance functions
- They perform same operations, generate the same IL code as well
- They can be used to wrap any task that requires to be executed as needed
While this is shown, there are a few differences to note here as well:
- The local functions have access to the function local variables as well
- For external functions, you have to manually pass the parameters; or at least use
ref
, out
parameters
- Local functions have direct connection to the variables of a function.
That said, there is another major point to talk about; 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 that, so that the compiler knows where that function is. What I mean to say here is, you can actually return a value before even writing the local function, and C# would compile the code and make it work properly as well.
int Process() {
var list = new List<int>();
var sum = Sum(list);
return sum;
int Sum(List<int> items) {
return items.Sum();
}
}
The IL code for this is:
Process:
IL_0000: nop
IL_0001: newobj System.Collections.Generic.List<System.Int32>..ctor
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call UserQuery.<Process>g__Sum1_0
IL_000D: stloc.1
IL_000E: ldloc.1
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 sum;
” at the end, there will be a nop
bytecode operation added to the IL, which as you may know is a no operation command, but might consume a cycle and so.
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 that 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 what, but, let’s just say it works.
static int Process() {
var list = new List<int>();
var sum = Sum(list);
return sum;
static int Sum(List<int> items) {
return items.Sum();
}
}
In most cases, the local functions seem 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. So the code that we have:
int Process() {
var list = new List<int>();
var sum = Sum(list);
return sum;
int Sum(List<int> items) {
return items.Sum();
}
}
Can be easily rewritten to the following one:
int Process() {
var list = new List<int>();
var sum = Sum(list);
return sum;
int Sum(List<int> items) => items.Sum();
}
Believe it or not, the change has a huge performance improvement over the older (function) way. The lambdas are definitely stronger. As we move onwards in the article, you will see how much of the concepts are being taken from the function programming… Tuples is one of such.
Tuple Improvements in C# 7
As you may know, tuple is a concept of functional programming, and are really very powerful and useful types; C# guys don’t know much of it, but function world such as Haskell have been using them for a while. Previously, System.Tuple
type was used to create the tuples. Before I actually go down, let me tell you that a tuple doesn’t only mean that you can “return more than 1 value from a function”. That is just one of the uses where a tuple proves to be useful, not all of it. Remember that I said it comes from the functional world, in that world, there are no objects and thus no classes and no instances. Thus, when you have to store something, you would typically be using a tuple that would show a record, or an entity — 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 the C# language syntax, so you do not have to write anything extra, and you still get the benefit of that. Also, the tuple is now a System.ValueTuple
instead of the System.Tuple
.
So, now let us see what they are and how they are useful, or not…
void Main()
{
var person = GetPerson();
Console.WriteLine($"{person.Item1} is {person.Item2} years old.");
}
(string, int) GetPerson() {
return ("Afzaal Ahmad Zeeshan", 21);
}
In the code above, what I am doing is, 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, what the code does is that it gets the value from the function and prints it on the console. Studying 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
IL_0008: ldstr "{0} is {1} years old."
IL_000D: ldloc.0
IL_000E: ldfld System.ValueTuple<System.String,System.Int32>.Item1
IL_0013: ldloc.0
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, then you will have a drastic change in the performance.
var (name, age) = GetPerson();
Console.WriteLine($"{name} is {age} years old.");
This code is a bit more readable for the people, but has an extra overhead for the program because now the program also has to push the variables on the stack.
- It loads the tuple type from the function.
- Maps the types to the named variable.
- Notice that the name and age variables are of different type, but they are both sharing the
var
type.
- Then after this, it continues doing the same work that it was.
So, the thing is, the default tuple ItemN
type does have a less readability but it gives you extra performance if you’re needing.
As for the IL, here is the IL for the code sample 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
IL_0016: ldloc.3
IL_0017: stloc.1
IL_0018: ldstr "{0} is {1} years old."
IL_001D: ldloc.0
IL_001E: ldloc.1
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 on 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 that 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, you can have any number of elements in the tuple. Interesting improvement! In the older ones, you would also be able to add a local tuple to access 8th to 14th tuple elements. However, they are not the cases here and you can directly access the element as container.Item8
.
(string, string, string, string, string, string, string, string, string, string, string) GetPerson() {
return (null, null, null, null, null, null, null, null, null, null, null);
}
So, if your code requires a type that has like 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 that 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 that type, that is also not a good option. In most cases, I myself 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 following code:
void Main()
{
var types = new object[] { "Afzaal", 21, 4.5d };
Process(types);
}
void Process(object[] list) {
foreach (var item in list) {
if(item is String) {
Console.WriteLine($"{(string) item} is String type.");
} else if(item is int) {
Console.WriteLine($"{(int) item} is int type.");
} else if(item is double) {
Console.WriteLine($"{(double) item} is double type.");
}
}
}
This code has a condition that checks if the type is matching, and then after that, casts it to the proper type to show the results. Whereas, if we use the C# 7 way of doing this, we would get the following results:
void Main()
{
var types = new object[] { "Afzaal", 21, 4.5d };
Process(types);
}
void Process(object[] list) {
foreach (var item in list) {
if(item is String str) {
Console.WriteLine($"{str} is String type.");
} else if(item is int i) {
Console.WriteLine($"{i} is int type.");
} else if(item is double d) {
Console.WriteLine($"{d} is double type.");
}
}
}
This code has somewhat better readability, as compared to the old one, and also saves us from an extra cast, because that is taken care of by the IL in the background. Making it easier for us to write the code, the same is the case for the switch
statements, as an example, let’s see how the integer check works in this case:
IL_0036: ldloc.2
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
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
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, then gets the value by unboxing it and then rest of the stuff is the same process, or formatting the string
and writing it. The thing is, we get saved in many ways, and the readability has improved.
As for the switch
statements, the following is the syntax:
switch (obj) {
case Person p:
break;
case Shape s:
break;
default:
break;
}
The improvement is, instead of having a primitive type here, you can now match the object type there — 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, providing you with a good way of range based switch
statement.
void Process(object[] list) {
foreach (var item in list) {
switch(item) {
case String str:
Console.WriteLine($"{str} is of String type.");
break;
case int i:
Console.WriteLine($"{i} is of int type.");
break;
case double d:
Console.WriteLine($"{d} is of double type.");
break;
}
}
}
This gives the same output and works like a charm in the case. Also, if you would like, you can add a condition to evaluate an integer value or any value based on a condition. Personally, I loved this feature as well because there were cases where one of the structures was unable be to be used, and one was not efficient… However, these improvements are bridging the gap actually.
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. What this means, is that you 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 a few things to note:
- This is a value type, instead of a reference type.
- The major reason was that, a reference type, as the name suggests, required to be instantiated and an object was created.
- However, the value type, if known, would only require a stack push… no other stuff required.
Even the documentation suggests that you should consider using the Task<T>
type instead of the ValueTask<T>
type. Only if you are aware that there is a performance improvement, then do that, otherwise keep using the Task
type.
However, deep somewhere, I do 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 that 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 converts the runtime object to a tuple. See how important the concept of tuples was before this section.
The function itself is:
void Deconstruct(out string param1, out int param2) {
}
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 that does this. If we create the function and then execute the following, it works:
void Main()
{
var person = new Person() { Name = "Afzaal Ahmad Zeeshan", Age = 21 };
var (n, a) = person;
Console.WriteLine($"{n} is {a} years old.");
}
public class Person {
public string Name { get; set; }
public int Age { get; set; }
public void Deconstruct(out string n, out int a) {
n = this.Name;
a = this.Age;
}
}
Removing the function causes an error, that tells the programmer that only a type that has the “Deconstruct
” function is able to undergo a deconstruction, otherwise add some helper functions, etc. If none, then you cannot use this 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
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 that were supplied to built it.
Most of the features of C# 7 are package based, and thus they require a package to be installed from the NuGet libraries. Whereas, a few of the improvements were installed natively — that literal one is such, and I hated these changes… Whereas, 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 I already did cover in the previous post of mine and were similar thus I left 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 and which are just magic tricks.
See you next time!