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
In .NET 11, a new public virtual method Type.GetNullableUnderlyingType() was added on System.Type, and Nullable.GetUnderlyingType(Type) now forwards to it. Custom Type subclasses (those derived directly or indirectly from System.Type) that do not override the new virtual will throw NotSupportedException when passed to Nullable.GetUnderlyingType, where they previously returned null.
Nullable.GetUnderlyingType inspected the input type using IsGenericType and GetGenericTypeDefinition, returning null for any type that wasn't a constructed Nullable<T>. Custom Type subclasses that did not specialize Nullable handling would silently return null.
classMyType:Type{/* ... */}Typet=newMyType();Type?u=Nullable.GetUnderlyingType(t);// returned null
New behavior
Nullable.GetUnderlyingType(Type) now forwards to the new Type.GetNullableUnderlyingType() virtual. The base implementation on System.Type throws NotSupportedException so subclass authors are required to opt in. Custom Type subclasses that do not override the virtual will throw:
classMyType:Type{/* does not override GetNullableUnderlyingType */}Typet=newMyType();Type?u=Nullable.GetUnderlyingType(t);// throws System.NotSupportedException:// "Derived classes must provide an implementation."
The in-box Type subclasses shipped by .NET (RuntimeType, TypeDelegator, TypeBuilder, EnumBuilder, GenericTypeParameterBuilder, TypeBuilderInstantiation, SymbolType, ModifiedType, the SignatureType family, and MetadataLoadContext's RoType) all override the new virtual and are unaffected.
Type of breaking change
Behavioral change: Existing binaries might behave differently at run time.
Reason for change
Nullable.GetUnderlyingType previously hard-coded a comparison against the runtime's typeof(Nullable<>). This produced incorrect results for Type subclasses that represent types from a different reflection universe — most notably MetadataLoadContext, where Nullable<T> was always reported as a non-nullable type (dotnet/runtime#124216). The new virtual gives each Type implementation a hook to identify Nullable types correctly within its own reflection model, mirroring the long-standing Type.GetEnumUnderlyingType() pattern.
The base implementation throws (rather than returning null) by design so that custom Type subclass authors are notified that they need to provide a correct answer for their domain.
Recommended action
If you author a custom Type subclass, add an override of GetNullableUnderlyingType():
If your subclass never represents a Nullable<T>, override to return null:
Description
In .NET 11, a new public virtual method
Type.GetNullableUnderlyingType()was added onSystem.Type, andNullable.GetUnderlyingType(Type)now forwards to it. CustomTypesubclasses (those derived directly or indirectly fromSystem.Type) that do not override the new virtual will throwNotSupportedExceptionwhen passed toNullable.GetUnderlyingType, where they previously returnednull.Tracking PR: dotnet/runtime#126905.
Version
.NET 11 Preview 4
Previous behavior
Nullable.GetUnderlyingTypeinspected the input type usingIsGenericTypeandGetGenericTypeDefinition, returningnullfor any type that wasn't a constructedNullable<T>. CustomTypesubclasses that did not specialize Nullable handling would silently returnnull.New behavior
Nullable.GetUnderlyingType(Type)now forwards to the newType.GetNullableUnderlyingType()virtual. The base implementation onSystem.TypethrowsNotSupportedExceptionso subclass authors are required to opt in. CustomTypesubclasses that do not override the virtual will throw:The in-box
Typesubclasses shipped by .NET (RuntimeType,TypeDelegator,TypeBuilder,EnumBuilder,GenericTypeParameterBuilder,TypeBuilderInstantiation,SymbolType,ModifiedType, theSignatureTypefamily, andMetadataLoadContext'sRoType) all override the new virtual and are unaffected.Type of breaking change
Reason for change
Nullable.GetUnderlyingTypepreviously hard-coded a comparison against the runtime'stypeof(Nullable<>). This produced incorrect results forTypesubclasses that represent types from a different reflection universe — most notablyMetadataLoadContext, whereNullable<T>was always reported as a non-nullable type (dotnet/runtime#124216). The new virtual gives eachTypeimplementation a hook to identify Nullable types correctly within its own reflection model, mirroring the long-standingType.GetEnumUnderlyingType()pattern.The base implementation throws (rather than returning
null) by design so that customTypesubclass authors are notified that they need to provide a correct answer for their domain.Recommended action
If you author a custom
Typesubclass, add an override ofGetNullableUnderlyingType():If your subclass never represents a
Nullable<T>, override to returnnull:If your subclass represents constructed generic types and may instantiate
Nullable<T>, return the corresponding type argument:If your subclass is a delegating wrapper around another
Type, forward the call:Compiling against .NET 11 surfaces the new virtual on
Type, making the override discoverable. No app/configuration switch reverts to the old behavior.Feature area
Core .NET libraries
Affected APIs
System.Nullable.GetUnderlyingType(System.Type)System.Type.GetNullableUnderlyingType()(new virtual; default throwsNotSupportedException)Note
This issue was drafted with assistance from GitHub Copilot.
Associated WorkItem - 573384