C# 9 brings many new big features and a few little goodies that are super useful and not enough known. They may not deserve a blog post of their own, but shouldn't be forgotten either. So here they are!

Module Initializers

This one is a significant pain point patched so far by some third parties tools, but the experience always seemed a bit hacky. It will be useful to have that capability natively supported in .NET.

Module initializers are very useful when you want to eager load something before anything else executes. I have mostly seen it on test libraries where you want to initialize something when starting a test run, even if you run one, many, or all tests in the assembly.

Eg. It could be used to override some static values for testing purposes.

To use that feature, tag a method with the ModuleInitializer attribute, and you are done! However, not all methods can support this feature as there's some restriction that must be met.

The method must:

  • be static
  • be parameterless
  • return void
  • not be a generic method
  • not be contained in a generic class
  • be accessible from the containing module (internal or public)

If the method meets all those criteria, it will be invoked when the assembly loads.

[ModuleInitializer]
public static void Magic()
{
    // Some powerful stuff here...
}

Simplified null checking

Nulls are a real plague in C#. Every time you use it as a shortcut because you are lazy, you are just buying yourself a great time debugging a cryptic error in production at 10 pm later on.

These production issues happen because null checks or null guards require a lot of boilerplate code to implement and good discipline to do it consistently. C# 9 addresses that very issue by providing a simplified syntax that will make it easier to guards us against the dreaded NullReferenceException. To use it, add a ! at the end of a method parameter name, and the compiler will automatically generate the proper IL behind the scene to check for nulls.

public void Before(string name)
{
    if (name is null)
    {
        throw new ArgumentNullException();
    }
}

// Exactly the same as
public void Now(string name!)
{
}

Covariant returns

Covariant returns now allow declaring a more specific type when overriding a base class method containing a less specific return type.

abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}

It's a massive improvement as you can now avoid a runtime cast of the method return value when using the child type (Tiger) and benefit from better IntelliSense at the same time.

var t = new Tiger();

// Before
Meat m = (Meat)t.GetFood();

// Now
Meat m2 = t.GetFood();

Performance and interop

Native size integer

The native sized integer is a new construct that allows the declaration of an int which the platform determines the size, either 32 or 64 bits. nint and nuint are the new keywords you need to use for that feature.

Some may point out that such capability already exists in the framework with IntPtr and UIntPtr. Native sized integers are just a wrapper on top of these types and provide additional capabilities like conversion and arithmetic operations, which are impossible with IntPtr.

Native sized integer can significantly optimize the performance of your application when doing an intensive calculation that requires low-level access and where every byte counts.