C#10 was released as part of .NET 6 and we see lots of syntactical improvements and I am sure you will also appreciate them.
Global Usings
Every single feature that you use from any package requires you to import it first. You can see in the below example the import of System.Text.Json to use JsonSerialize function. What if there are more files where you need the same functionality?
Just create a common file (any name) to store all imports and the compiler will understand it. Add a global keyword and that’s it.
In case you don’t want to create a separate file then you can add using statement in .csproj file as well.
Now you no longer need to import it. It would be a great relief where you have a long list of imports.
You can also toggle this feature on/off from the properties file. By default, it is on and that’s the reason you don’t need to import framework related imports like System for Console.Writeline.
File Scoped Namespaces
Earlier whenever you used to create a new class, you would see it wrapped in namespace’s curly braces.
This is helpful if you want to add multiple namespaces in the same file as shown below.
Let me tell you that in my career, I have never seen such a need. If we are going to have only one class then there is no meaning in adding curly braces and for this reason, you won’t need them anymore in C# 10.
Constant Interpolated Strings
Imagine you want to have all API routes in one place. Generally, for that, we can create a static class as shown below. If you notice carefully then I am concatenating two strings using ‘+’ operator to form a URL.
Though this approach works fine, if you prefer string interpolation and want that then you need to replace your const code with static readonly. Why? Because if you want your constants to be usable by attributes (like Route, HttpGet, etc) then you are forced to use const which means you cannot use string interpolation with constants in C#9.
But now, there is no such restriction.
Lambda Improvements
Earlier, writing lambda expressions required you to mention return types explicitly.
But now, the compiler has become smart enough to infer the type based on your return values. Considering the heavy usage of lambdas everywhere, it is really a good improvement.
Saying that, if you make it harder for a compiler to guess the return type, then it won’t work.
The only way to overcome this is either resort to the old way of writing lambdas or cast it to a higher type.
Extended Property Pattern
C# introduced pattern matching in C# 7.0. Since then, each major C# version extends pattern matching capabilities. Pattern matching is a technique where you test an expression to determine if it has certain characteristics. C# pattern matching provides a more concise syntax for testing expressions and taking action when an expression matches.
One such pattern introduced in C# 8 is Property Pattern. It checks if an expression’s properties or fields match a nested pattern.
Now the same pattern written on line 7 can be rewritten with. (DOT) notation. A very small improvement, but it improves readability.
Record Structs
C# had classes from the beginning but they are reference types. After the advent of concepts like DDD we soon realized the importance of value objects as they are considered safer due to their immutability nature. For this, we have structs to fulfil the needs. Though structs are good in some scenarios, they had limitations, like they don’t support inheritance, consume more memory since every instance has a complete copy of all of the data.
We needed the best of both worlds: inheritance and reference from classes and value type objects from structs. So, records took birth in C#9. They can be mutable and immutable both based on how you create them. They are fast when comparing two different instances of the same record as compared to the equality feature of structs. It is similar to data classes in Python and Kotlin.
When you declare record this way, by default it is of class type.
It is as same as shown below.
But what if you need the features of both record and struct? Confusing? I mean to say the equality feature (because it’s faster than struct’s equality feature) of record and value type of struct. Thus we have a record struct now.
Record Types Can Seal ToString() Method
One of the aesthetic features of Record is its compiler-generated ToString() method. It returns output in the following way.
<record type name> { <property name> = <value>, <property name> = <value>, …}
Person { FirstName = Amit, LastName = Chaudhary, Children = System.Collections.Generic.List`1[System.String] }
If the value is of reference type, it will return its Type rather. So, we might need to override the default ToString() method in the record if we want to print the reference type. Well, this was possible till now. Now, what if there is one more derived class that wants to override your record’s ToString() method but you don’t want to allow that? In C# 10, we can seal ToString() method in records.
Structure Type Improvements
You can now declare an instance parameterless constructor in a struct type and initialize instance fields or properties at their declaration. Also, a left-hand operand of the with expression to create another updated object can be of any structure type or an anonymous (reference) type.
Parameterless Constructor
With Expression
Declaration and Initialization in Same Statement
Deconstructing an object required declaration and initialization in different statements. You could not mix both. But that restriction is removed in C# 10.
In C# 9
In C# 10
Conclusion
Apart from the above features, there are two more which are Allow AsyncMethodBuilder through which you could write your own custom async method builder for advanced performance tuning and static abstract members in interfaces which helps to build mathematical algorithms. Let’s switch to C# 10 now!!!