Skip to content
Ian Griffiths By Ian Griffiths Technical Fellow I
C# 8.0 nullable references: MaybeNull

As with all the other entries so far in this sub-series on attributes for getting better results from C# 8's nullable references feature, the goal is to enable the compiler to improve depth and precision when detecting null-related programming errors: these attributes all make it possible to find more mistakes, while also lowering the chances of a false positive.

The subject of this post is useful for revealing information about possible nullness that might otherwise have been lost as a result of the use of generics.

Last time, we looked at NotNull. This looked superficially similar to DisallowNull, but whereas DisallowNull made a statement about the prerequisites for being allowed to use a member, NotNull tells the compiler a post-condition that can safely be deduced.

Today we're going to look at MaybeNull, which has a similar relationship to AllowNull: whereas AllowNull tells the compiler about a prerequisite, MaybeNull tells the compiler about a post-condition—something it can infer if the relevant method returns.

The Introduction to Rx.NET (v6.1) 3rd Edition (2025) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

Expressing nullability for generic type arguments

When you first try to enable nullable references for generic code, you will typically run into a problem: you will find yourself wanting to express the nullability of the usual entities (parameters, return types, etc.), but using a generic type parameter.

We've already seen how AllowNull lets us deal with this problem for arguments where we pass information in, but what about when information flows in the other direction?

For example, suppose you have some method that attempts to find an item that matches certain criteria—this might want to return null if nothing matches. So the obvious return type would be T?. But if you write that, you'll find it doesn't work.

This is symptomatic of the fact that nullability has been added rather late in the day. C# 1.0 just had reference types (always nullable) and value types (never nullable). Then C# 2.0 added the ? suffix for value types, introducing things like int? to the type system.

Now, C# 8.0 has retrofitted this idea to reference types, so that just like value types they come in nullable (string?) and non-nullable (string) forms.

Unfortunately, because of the history of these developments, they work in completely different ways. The meaning of T? is radically different when T is a value type from when T is a reference type. (With value types, the runtime representation changes completely: instead of just, say, an int, you get a Nullable<int>, which is actually a combination of an int and a bool.

But with reference types, the runtime representation for nullable and non-nullable references is identical.) And the MSIL that needs to be generated for these two cases is consequently also quite different.

So if you want to be able to write T? for a type parameter, you need to constrain it to be either a value type or a reference type, so that the C# compiler knows which kind of code to generate.

Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

But that's a huge limitation. Consider what this might mean for the LINQ FirstOrDefault operator: that needs to be able to return nothing in cases where the source is empty, so logically its return type wants to be T?.

But to be able to have that return type, it would need to constrain T to be either value types only, or reference types only.

That would be a huge breaking change, and extremely limiting.

So instead, we express the nullability for uses of generic type arguments differently. Methods such as FirstOrDefault just use a return type of T, but they are annotated with [return: MaybeNull] to indicate that in cases where someone has supplied a non-nullable reference type as the type argment, the return type should be treated as the nullable form of that type.

So if you have an IEnumerable<string> (and assuming that you're in a nullable annotation context, this promises that if the enumerable contains anything, everything it contains will be non-null), the effective return type of FirstOrDefault is string?

You can use MaybeNull on properties and out arguments—anywhere that the type may be returned, in other words.

Note that if you apply MaybeNull to a read/write property, this does not indicate that callers are allowed to set it to null. This attribute is strictly a statement about the result.

If you want to indicate that it is also acceptable to set it to null, you would apply both MaybeNull and AllowNull.

FAQs

Why can't you write T? for an unconstrained generic type parameter in C#? The meaning of T? is radically different for value types versus reference types. For value types, T? creates a Nullable with a completely different runtime representation. For reference types, the runtime representation is identical. The MSIL generated for these cases is completely different, so C# requires you to constrain T to either value types or reference types before using T?. (Note that later versions of C# added more subtle handling that meant in some cases you can now write T?.)
What problem does the MaybeNull attribute solve? MaybeNull solves the problem of expressing nullable returns for generic type arguments when you cannot write T?. It indicates that even when someone supplies a non-nullable reference type as the type argument, the return value should be treated as the nullable form of that type. This is essential for methods like LINQ's FirstOrDefault.
How does FirstOrDefault use MaybeNull to indicate its return type might be null? FirstOrDefault needs to return default(T) (which will be null if T is a reference type) when the source is empty, but cannot use T? as its return type without constraining T. Instead, it uses a return type of T with [return: MaybeNull], indicating that when a non-nullable reference type is supplied as the type argument, the return should be treated as nullable. For IEnumerable, the effective return type becomes string?.
What is the relationship between MaybeNull and AllowNull attributes? Both deal with nullability for generic type arguments, but from different perspectives. AllowNull is about accepting null inputs (parameters), while MaybeNull is about outputs that may be null. If you have a read/write property where both getting and setting can involve null, you would apply both attributes.
Can MaybeNull be applied to properties, and what does it mean for read/write properties? Yes, MaybeNull can be applied to properties and out arguments. For a read/write property, MaybeNull only affects the getter, indicating the returned value might be null. It does not indicate that callers are allowed to set it to null. To allow null values to be set, you must also apply the AllowNull attribute.

Ian Griffiths

Technical Fellow I

Ian Griffiths

Ian has worked across an extraordinary breadth of computing - from embedded real-time systems and broadcast television to medical imaging and cloud-scale architectures. As Technical Fellow at endjin, he brings this deep cross-domain experience to bear on the hardest technical problems.

A 17-time Microsoft MVP in Developer Technologies, Ian is the author of O'Reilly's Programming C# 12.0 and one of the foremost authorities on the C# language and high-performance .NET development. He's a maintainer of Reactive Extensions for .NET, Reaqtor, and endjin's 50+ open source projects.

Ian has created Pluralsight courses on WPF fundamentals, WPF advanced topics, WPF v4, and the TPL, and has given over 20 talks at conferences worldwide. Technology brings him joy.