Skip to content
Ian Griffiths By Ian Griffiths Technical Fellow I
C# 8.0 nullable references: when methods don't return

This is the last in the sub-series on attributes for getting better results from C# 8's nullable references feature. (Next time I'll be getting back to some other aspects of nullable references.) As always, the goal here is to annotate our code with attributes that enable the C# compiler to do a better job with its nullable reference analysis—detecting more real problems, and reporting fewer false positives. The attributes I'm describing in this post help handle situations where a method might never return (e.g., because it throws an exception). The compiler needs to know when this can happen, because otherwise, it could incorrectly think that we're attempting to dereference a value that could be null.

With all the other posts in this sub-series, I've described attributes that were added specifically for nullable reference support. The attributes I'm describing here have been around since long before C# 8 added nullable references. They were originally designed to serve other purposes, but now have an additional role in a nullable-references-aware world.

DoesNotReturn

Earlier in this series I wrote about NotNull. This was primarily concerned with telling the compiler what it could infer about its inputs once the method returns, but as I also showed, it can be used in cases where a method might never return.

The DoesNotReturn attribute is also concerned with methods that don't return. But unlike NotNull which lets the compiler infer what will be true if the method returns, DoesNotReturn is simpler: it indicates that the annotated method will not return under any circumstances. This is typically used for methods that throw exceptions.

C# 8's nullable reference checking depends on the flow control analysis that C# has had since v1.0. This analysis supports the "definite assignment" rules that warn you when you might be trying to use a variable that has not yet been initialized. Some of the analysis the compiler was already performing for those purposes also has an impact on null handling warnings. This is why the DoesNotReturn attribute, which has been around for a long time, can now have an impact in null-aware code, as this example shows:

public static int Measure(string? pieceOf)
{
    if (pieceOf == null) { ThrowHelper(); }
    
    return pieceOf.Length;
}

[DoesNotReturn]
private static void ThrowHelper() => throw new ArgumentNullException();
Programming C# 10 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

This is somewhat contrived, but in codebases that localize exception messages, the use of helper methods to throw exceptions is common. Without the DoesNotReturn attribute, the pieceOf.Length expression will cause a compiler warning. But with that attribute present, the compiler correctly deduces that that line can only be reached if pieceOf is non-null (exactly as it would have done if the exception had been thrown directly from this method, without using a helper).

DoesNotReturnIf

The similar DoesNotReturnIf can be applied to a parameter, along with a boolean value indicating that the method will only fail to return if the argument in question has the specified boolean value. This is even closer in spirit to NotNull, in that it indicates that the method won't return under certain circumstances. But in this case, it's based purely on a boolean value, and not the nullness. Even so, the compiler's flow analysis can sometimes deduce null-related information from this attribute, as in this example:

private static void Test(string? arg)
{
    OnlyReturnsIfTrue(arg != null);
    Console.WriteLine(arg.Length);
}

private static void OnlyReturnsIfTrue([DoesNotReturnIf(false)] bool flag)
{
    if (!flag)
    {
        throw new InvalidOperationException();
    }
}
The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

If the DoesNotReturnIf attribute were not present, this example would produce a warning on line 4—args.Length dereferences a variable of type string?. However, the attribute lets the compiler know that the method will not return in the case where its argument is false, and in this example, that means it won't return if arg is null. This enables the compiler to deduce that if the method does return, args must be non-null, and that it should not raise a warning for args.Length at that point in the code.

Conclusion

This is the last entry in the subseries on attributes that can improve the usefulness of C# 8's nullable references feature. Each of these enables us to make more detailed statements about our code's handling of nulls, improving the detail and quality of the compiler's analysis. Here's a quick roundup of each of them:

  • Overview – a quick tour round all of the attributes (the TL;DR version of the series)
  • AllowNull and DisallowNull – expressing distinctions the type system cannot capture for inputs
  • NotNull – enabling the compiler to infer that a reference is not null at a particular point in the code, even when the type system indicates that it is nullable
  • MaybeNull – indicating potentially null outputs in generic code regardless of the nullability of the type arguments
  • NotNullWhen, MaybeNullWhen, and NotNullIfNotNull – enabling the compiler to infer (non-)nullness conditionally
  • DoesNotReturn and DoesNotReturnIf (this post) – avoiding false warnings by letting the compiler know when methods won't return

Ian Griffiths

Technical Fellow I

Ian Griffiths

Ian has worked in various aspects of computing, including computer networking, embedded real-time systems, broadcast television systems, medical imaging, and all forms of cloud computing. Ian is a Technical Fellow at endjin, and Microsoft MVP in Developer Technologies. He is the author of O'Reilly's Programming C# 10.0, and has written Pluralsight courses on WPF (and here) and the TPL. He's a maintainer of Reactive Extensions for .NET, Reaqtor, and endjin's 50+ open source projects. Technology brings him joy.