You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Nullable.GetUnderlyingType(Type) currently uses ReferenceEquals(genericType, typeof(Nullable<>)) to identify nullable types. This only works for RuntimeType instances — it fails for types from MetadataLoadContext (and any other Type subclass), returning null even for valid Nullable<T> types.
This is the same class of problem that Enum.GetUnderlyingType had before Type.GetEnumUnderlyingType() was added — a static helper that only works with runtime types, with no way for custom Type implementations to provide correct behavior.
The cascading impact is significant: NullabilityInfoContext calls Nullable.GetUnderlyingType() at three callsites, so MetadataLoadContext types get completely wrong nullability analysis (see #124216 for details).
API Proposal
namespaceSystem;publicabstractclassType{/// <summary>/// Returns the underlying type argument of a <see cref="Nullable{T}"/> type./// </summary>/// <returns>/// The type argument if the current type is a closed generic Nullable{T};/// otherwise, null./// </returns>publicvirtualType?GetNullableUnderlyingType();}
Nullable.GetUnderlyingType(Type) would then forward to the new virtual:
// Works the same as before for runtime types:Type?underlying=typeof(int?).GetNullableUnderlyingType();// System.Int32typeof(int).GetNullableUnderlyingType();// null// Now also works for MetadataLoadContext types:usingvarmlc=newMetadataLoadContext(resolver);Assemblyasm=mlc.LoadFromAssemblyName("System.Runtime");TypemlcNullableInt=asm.GetType("System.Nullable`1")!.MakeGenericType(asm.GetType("System.Int32")!);Type?underlying=mlcNullableInt.GetNullableUnderlyingType();// the MLC Int32 type// Nullable.GetUnderlyingType forwards to the virtual, so it also works:Nullable.GetUnderlyingType(mlcNullableInt);// the MLC Int32 type
Internal virtual IsNullableOfT on Type — Similar concept but internal-only. Less useful since the community cannot provide implementations for custom Type subclasses.
Risks
This is a new public virtual method on Type, which is a widely-subclassed type. The default implementation returns null for non-Nullable<T> types, which is safe for existing subclasses.
Matches the established precedent of Type.GetEnumUnderlyingType().
Proposed implementation
See PR #125356 for a complete working implementation with tests.
Background and motivation
Nullable.GetUnderlyingType(Type)currently usesReferenceEquals(genericType, typeof(Nullable<>))to identify nullable types. This only works forRuntimeTypeinstances — it fails for types fromMetadataLoadContext(and any otherTypesubclass), returningnulleven for validNullable<T>types.This is the same class of problem that
Enum.GetUnderlyingTypehad beforeType.GetEnumUnderlyingType()was added — a static helper that only works with runtime types, with no way for customTypeimplementations to provide correct behavior.The cascading impact is significant:
NullabilityInfoContextcallsNullable.GetUnderlyingType()at three callsites, so MetadataLoadContext types get completely wrong nullability analysis (see #124216 for details).API Proposal
Nullable.GetUnderlyingType(Type)would then forward to the new virtual:API Usage
Alternative Designs
Name-based fallback in
Nullable.GetUnderlyingType— UseNamespace == "System" && Name == "Nullable\1"` as a fallback. Rejected because it would match user-defined types with the same name (raised by @jkotas in Fix Nullable.GetUnderlyingType for MetadataLoadContext types #125356).Internal virtual
IsNullableOfTonType— Similar concept but internal-only. Less useful since the community cannot provide implementations for customTypesubclasses.Risks
Type, which is a widely-subclassed type. The default implementation returnsnullfor non-Nullable<T>types, which is safe for existing subclasses.Type.GetEnumUnderlyingType().Proposed implementation
See PR #125356 for a complete working implementation with tests.