Proposal: Non-Nullable Reference Types and Generics #403
Replies: 3 comments
-
The above proposal is adapted from my previous non-nullable reference types proposal dotnet/roslyn#4443. However, that proposal was based on explicitly annotating declarations as not nullable via |
Beta Was this translation helpful? Give feedback.
-
I really think @jeffanders approach is the best way to solve this issue. It provides backwards compatibility (as null-ability is assumed by default and you opt in to non-nullable) The only counter argument to it, as far as I can see, is the fact it differs to value types (which assume non-nullable by default, the opposite) |
Beta Was this translation helpful? Give feedback.
-
@andrewm1986
The reason the team considered and rejected that approach is because non-nullability is, by far, the majority case. It would make considerably more sense to reduce the amount of adornment required to adopt this feature. |
Beta Was this translation helpful? Give feedback.
-
Non-Nullable Reference Types and Generics
The current proposal for nullable reference types in C# (#36) only briefly touches on generics and leaves a number of open questions. This proposal attempts to more fully consider how non-nullable reference types will work with generics. This proposal is only applicable to an assembly that has opted-in for non-nullable references.
Nullability
Every variable will be assigned a nullability. There are three possibilities:
This forms a nullability hierarchy with regards to compiler enforcement of nullability gurantees from weakest to strongest.
Preserving can only apply to variables declared with a type of a generic type parameter as explained in detail later. All other variables are either nullable or non-nullable.
Value and reference types are non-nullable while
Nullable<T>
(e.g.int?
) and reference types with the nullable modifier (e.g.object?
) are nullable.A variable may only be assigned a value that has an equal or stronger nullability (the assignment strength rule).
Generic Type Parameters
Generic type parameters subject to a struct (or equivalent) constraint are not affected by this proposal. For example
All other variables of type corresponding to a generic type parameter are preserving. Variables of a preserving type also follow the non-nullable rules within members for which the type parameter except for the assignment strength rule. This includes disallowing the use of the default operator. In all other members the type parameter will preserve the nullability of the type argument.
Variables declared with the type of a generic type parameter not constrained to be a struct can now use the nullable modifier. For example:
It is important to note that the nullable modifier is only encoded via an attribute and it therefore does not affect the runtime or assembly representation of
T
. i.e.string
andstring?
are specialised toIdentityD<string>
andint
andint?
toIdentityD<int>
andIdentityD<int?>
respectively at runtime.Preserving type parameters also facilitate preserving the semantics of most existing generic code with the occasional addition of a nullable modifier (which will not impact consumers on C# compilers that don't understand non-nullable types)
For example FirstOrDefault can naively be implemented as follows:
IEnumerator<T>
would likely be updated so that the Current property would beT?
as shown below since typical implementations would returndefault(T?)
if MoveNext returned false or had never been called (but that behaviour is specifically undefined). Alternatively leaveCurrent
asT
and implementations may need to castT?
toT
insideCurrent
(which is again already covered by the existing contract of undefined behaviour).If a preserving type parameter A is used as a constraint for another type parameter B then B must also be preserving (e.g. B may not be constrained to be a struct). Further it also constrains the permitted nullability of type argument B to be stronger than or equal to the nullability of type argument A. For example
When inferring the effective nullability of a preserved type parameter
T
for a method invocation where the type parameter is used with multiple parameters then the effective nullability will be the weakest effective nullability of the argument supplied for parameters of typeT
. For example consider the following method and invocations:Other Considerations/Options
The approached presented here is forwards compatible with existing C# compilers or when non-nullable references have not been opted-in. However for the benefit of a non-nullable aware C# compiler a number of library changes would be necessary to include the nullable attribute against certain fields, parameters and return values for example (such as the
FirstOrDefault<T>
andIEnumerator<T>.Current
examples above). This would require a new version of the BCL and other libraries which might not be possible. A mechanism might be required to allow a compile time only companion assembly for any assembly. This would be similar toTypeForwardedToAttribute
but only for compile time and would only be for the purposes of constructing a union of applied attributes.When overriding a virtual member that uses a preserving type parameter with the nullable modifier (i.e.
T?
) the signature must match the original virtual declaration as intervening overrides in other assemblies might have been compiled without knowledge of/support for non-nullable references.This proposal disallows
default(T)
whereT
is a preserving type parameter and instead you must usedefault(T?)
. We could simply interpret the former as the latter which might simplify porting existing generic code where the use of the default operator may already be prevalent)It is worth considering whether it is valuable for type parameters to be constrained to only be non-nullable. For example:
The use cases over beyond what preserving type parameters provide seem minimal and it adds extra complications involving inheritance and overriding virtual members.
Beta Was this translation helpful? Give feedback.
All reactions