Another year another C# version. Of course, the version just released is 12 but I am going to share my impressions of version 11 after using it for a year. As is my new policy I will only share my impression while leaving
the explanation of what they do to Microsoft.
Generic Attributes
You now get to use generics in attributes. This replaces the [Attribute(typeof(SomeType))] pattern. I have not had the chance to use it yet but I feel it will be nice but not critical. I guess libraries are still adapting to the feature and this is why I have not encountered it in practice yet.
Required Members
This bullshit feature was only added because some feature are addicted to the object initializer syntax and cannot use constructors to you know... construct objects if their life depended on it. They got init-only members in C# 10 and then found out they want required members to be able to properly use nullable reference types and make immutable types. I hate the feature with a passion, it pushes people in the wrong direction. I tried using it in the places where I cannot use records and it did not work. The common place is EF. Adding required to the DbSets did not work :( I tried putting required instead of = null! on non-nullable properties but turns out I create my entities in stages, some properties come from the mapping to a DTO, others like the timestamp are added in the service code so tough luck. Not only does this feature push people in the wrong direction but I have yet to find a single use for it in my projects.
Generic math support
This feature allows static virtual members in interfaces and the cool usage is operator overloading. The standard library now comes with a bunch of algebra interfaces in
System.Numerics like IAdditionOperators and many others. These interfaces are implemented by numeric types. The interfaces create a hierarchy culminating with INumber and are subject to the standard algebra rules. This allows amazing things like defining algebraic
rings and
fields with proper operators and then have code that works on fields. Imagine code that can solve an equation and does not care if the type is a number, a matrix or a polynomial. Well… not that I do anything of the kind in my daily life :(
List Patterns
With every version C# extends its pattern matching capabilities. This time around it is list patterns, the ability to decompose arrays and lists. You can have a list like [1, 2, 3, 4] and match it like this [_, var a, .. var tail] then you get the number 2 in the a variable and a list containing [3, 4] in the tail variable. In performance critical scenarios if this feature produces new lists it might create problems. Maybe make sure you use it with ReadOnlySpans in these cases. As always, I welcome enhancements to pattern matching. In addition, you can now pattern match ReadOnlySpan<char> and Span<char> on a constant string which is great for all those high-performance parser-like scenarios.
Raw String Literals
Raw string literals allow programmers to write multiline string without escaping anything. That works by starting and closing the string with at least three double quotes. If your string has three double quotes inside it you start/close it with four. No matter how many you have in the string you can add one more double quote. These work nicely with indentation by ignoring spaces on consecutive lines until the column where you open the quotes start. Great feature that I already used on several occasions.
File Local Types
There is now a "file" access modifier that can be applied to types. It does what you expect – the type is only available in the current file. That sounds like a useless feature and it is indeed something you never want to do as a human writing code but it is useful for code generation. Generators do not need to worry if a type conflicts with the name of another type elsewhere. They can declared their helper types file local and simplify the process. Nice addition considering the rise of metaprogramming in C#.
Small Things
- nint and nuint – native pointer sized integer aliases (for System.IntPtr and System.UIntPtr). Probably important for people who need to write performant low level code, like the people who write the .NET itself
- string interpolation now allows new lines between the braces. This means that C# code with new lines can now be inside the code part of the string. Never thought to use it but I guess it makes sense to be possible. I tend to prefer to create a variable and put it in the string rather than write C# there because then it is hard to follow what the string says. I do not think I will use this feature much but it is better if it works as expected
- Auto-default struct – previously structs with constructors needed to initialize all fields. Now the compiler will initialize any field you miss. Not sure what I think of this and I haven't had a chance to use it in practice. Usually I like more explicit stuff.
- nameof now can be used in attributes. VS prompted me to use it on several occasions with nullability attributes. It is good.
- ReadOnlySpan<byte> literals can now be declared as UTF8 string literals by using "the"u8 suffix. I have not used them but I know this is very useful for the ASP.NET team.
- ref fields and ref scoped variables – ref fields can be added to a ref struct to store Spans and friends. I tried some Spans programming and because you cannot assign it to fields of classes it can be annoying so more tricks to somehow carry it around are welcome.
That is all for this version. See you next year with the C# 12 recap