|<<>>|2 of 214 Show listMobile Mode

C# 9: finally, covariant returns

Published by marco on

The article Welcome to C# 9.0 by Mads Torgersen (Microsoft Dev Blogs) (May 2020) introduces several nifty new features that I am really looking forward to using.

What about C# 8?

I still haven’t moved Quino to C# 8, as the only feature I’d love to have there is the non-nullable types, which ReSharper Annotations provide with earlier versions of C#. Not only that, but the nullabilities are properly propagated to users of Quino. It’s understood that recent versions of Visual Studio and runtimes and compilers also do this but, until recently, our customers weren’t up-to-date yet.

In C# 8, we could also replace extension methods with default interface methods—but we’ve also been replacing almost all extension methods in Quino with singletons and composition anyway. A lot of the rest of the features are nice, and interesting, but they are targeted optimizations that don’t really apply to a lot of the code that I write. I see how they are eminently useful for lower-level library and runtime optimization—many are clearly made to be able to handle web requests and fine-grained tasks more quickly and without allocation

Features in C# 9

Still, the features in C# 9 make an upgrade even more attractive.

  • Init-Only Properties can only be initialized in the object initializer, after which they are immutable. This extends read-only properties, which can only be initialized to default values or in the constructor to make them much more useful and allow many, many more data structures to be immutable.
  • Records reduce a ton of boilerplate for what used to be referred to as DTOs. The declaration support is very similar to the syntax in Typescript. Record classes automatically get construction, deconstruction, and value-based equality and hash-code support for a very natural way of declaring and working with immutable data.
  • The with keyword and functionality allows code to easily derive new data from existing data (e.g. var originalPerson = otherPerson with { LastName = “Hunter” };)
  • There’s some neat improvements to pattern-matching with relational patterns and logical patterns, but I honestly don’t expect to use that too much (my use of advanced pattern-matching so far has been relatively limited…I haven’t even availed myself of the extended support in C# 8).
  • There are improvements to target-typing with some coalescing expressions now compiling as expected rather than requiring what always felt like a superfluous cast. I doubt I’ll be using target-typing like this: Point p = new (3, 5); rather than this: var p = new Point(3, 5);
  • And, finally, covariant return types make an appearance. Java has had these for forever and there is no logical downside to introducing them.

    This allows a descendant method to change the return type of an override to a descendant as well. The most common use case would for the return type of a Clone() method. The next step would be to allow anchored types (as in Eiffel), which would let a method declare its return type as like this and remove the requirement that each descendant override Clone at all, while still having the desired covariant return type.

    I’ve been musing about these features for what feels like most of my career.