Yeah, I know C# 11 was released weeks ago but I have not written about C# 10 yet! I have been working with C# 10 for a year and it is time to share my impressions. Of course I did not use every feature, especially the low-level ones and in these cases I will share my opinion guided by my expectations about how I would feel if I used it. I will not explain what the features do but you can follow along with Microsoft's
explanation of the features here Record Structs
Record structs are not as useful as record classes because value types do get structural equality and in general we use classes much more than structs but still it is good to have the other goodies out of the box. It also feels like simplifying the language because you don't need to wonder why you can have record reference types but not value types. It is simplification by unification.
Improvements of Structure Types
Speaking of structs we can now declare parameterless constructors. I haven't used this feature but I remember that I have tried to declare such a constructor in the past and was surprised that the compiler wouldn't allow it. One thing to watch for is that when declaring arrays or fields the default value will skip the parameterless constructor and will simply assign the default values of all struct fields. Oh, and BTW the "with" operator now works with any struct which is great. I can't get enough of the with operator.
File-scoped Namespaces and Global Usings
Hands down the best features of C# 10. The file-scoped namespace allows developers to declare the namespace without the block (i.e. the { } for scope) and it affects the whole file. Global usings allows one to declare using directives in a special file or in the project file and these using directives are applied to all .cs code files in the project. This cleans up code files so much removing four characters indentation on each line and a bunch of lines from the top of the file. The tooling is not great yet as it can't clean or even mark namespaces in your file which are already added globally so you have to take care to remove them manually but the end result is great, some files with smaller DTOs and Entities are half the size. This is also a feature every codebase benefits from, from students to the biggest enterprise projects.
Extended Property Patterns
You can now do { Prop1. Prop2: pattern } instead of { Prop1: { Prop2: pattern } }. Minor improvement.
Lambda Expressions Improvements
Lambdas can have a natural type (when it can be inferred), they can declare return type (for the cases when the natural type cannot be inferred) and they can be decorated with attributes. A minor good thing I guess. They needed these to make the so called Minimal APIs work better but this will open the door for other libraries too.
Constant Interpolated Strings
You can now use string interpolation with const string when the placeholders are filled with other const strings which makes the language more consistent but sadly you can't do that when the placeholders are filled with numbers because apparently they didn't want to pick the culture for that number string representation. In my opinion should be made to work for ints but not doubles because of the decimal separator.
Records Can Seal ToString()
Apparently you could not seal ToString() on records when overriding it. Now you can.
Assignment and Declaration Deconstruction
This is now allowed and it wasn’t before:
int x = 0;
(x, int y) = point;
Improved Definite Assignment
Apparently they improved the definite assignment warnings when using null checking. It didn't catch some cases where there was comparison with bool literals. Why is anyone comparing with bool literals?
Allow AsyncMethodBuilder Attribute on Methods
Previously you could specify how the async/await code is generated for a specific task-like type. Now you can control this on a method level.
CallerArgumentExpression Attribute Diagnostics
This is a way to get the expression that was passed in another argument as a string from the compiler. It looks like this
public static void Validate(bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
if (!condition)
{
throw new InvalidOperationException($"Argument failed validation: <{message}>");
}
}
Validate(3 == 5);
will result in an InvalidOperationException with message "Argument failed validation: 3 == 5". I imagine this can be quite useful for validation, logging and testing libraries.
One comes to the conclusion that this release did not have a big, complex feature that requires a lot of work but the file scoped namespaces are very big improvement and the small features are also welcome. There is no controversial feature in this release.