In my last blog in this series, I talked about the AllowNull attribute. This post shows its converse,
DisallowNull. As with all the attributes relating to nullable references, the goal is to improve expressiveness, enabling the compiler to report more nullable-related problems without producing spurious warnings.
DisallowNull is pretty much the mirror image of AllowNull: whereas
AllowNull lets you state that a null is allowed even when the type seems to state otherwise,
DisallowNull lets you state that a null is not allowed even when the type system seems to state that it would be allowed. Much like
DisallowNull is used with asymmetric properties, and generics.
Occasionally, you might define a property that returns null if it has not been set yet, but where if someone does set it, they must provide a non-null value. You cannot express this through the C# type system alone, because a property's get and set accessors must have the same type. If you use a nullable type, you won't get warnings when code sets the property to a null value. If you use a non-nullable type, you won't get warnings for code that uses the property without checking for null.
DisallowNull resolves this dilemma. You can define a property with a nullable type, and then apply the
DisallowNull attribute. The nullability of the property's type means the compiler will flag any attempts to use the value returned by the property without first checking it for null. But the presence of this attribute means it will also report any attempts to set it to a value that might be null.
DisallowNull also gets used in generic code. It enables you to state that a particular usage of a type parameter does not accept null even when a nullable type is supplied as the type argument for that parameter. You can see this in use in the
IEqualityComparer<T> interface from the .NET class library (which, as it happens, uses both
GetHashCode method here indicates that it does not accept nulls. So even if we instantiate this as
IEqualityComparer<SomeClass?>, the effective signature of
GetHashCode makes the argument type
SomeClass (i.e., non-nullable) and not the
SomeClass? (nullable) specified in the generic type argument. (Meanwhile, the arguments for
Equals are both nullable regardless of whether the type argument is nullable or non-nullable.)
This is consistent with the non-generic version of the interface:
Here it's clear that the
Equals method accepts nulls but that
GetHashCode does not. This non-generic definition expresses this more directly through the presence or absence of
?, but the generic interface needs to use the
DisallowNull helps us out in two scenarios that are very similar to ones where its converse,
AllowNull, is useful. One is where a type is stated once, but used in multiple ways, and we want different nullability in different places. The usual example of this is a property: we only get to declare the type of a property once, but we may require different nullability for the get and set accessors. Generics may also create scenarios where a type is expressed just once (as the type parameter) but then used in different ways. And then there's a second scenario, specific to generics, in which we want to be able to say that in cases where the argument for a type parameter is a reference type, we want the non-nullable version of that type, regardless of the nullability of the type argument.