Repository navigation
Description
Description
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.
Tracking PR: dotnet/runtime#126905.
Version
.NET 11 Preview 4
Previous behavior
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.
class MyType : Type { /* ... */ }
Type t = new MyType();
Type? u = Nullable.GetUnderlyingType(t); // returned nullNew 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:
class MyType : Type { /* does not override GetNullableUnderlyingType */ }
Type t = new MyType();
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 returnnull:public override Type? GetNullableUnderlyingType() => null;
-
If your subclass represents constructed generic types and may instantiate
Nullable<T>, return the corresponding type argument:public override Type? GetNullableUnderlyingType() { if (IsGenericType && !IsGenericTypeDefinition && GetGenericTypeDefinition() == typeof(Nullable<>)) { return GetGenericArguments()[0]; } return null; }
-
If your subclass is a delegating wrapper around another
Type, forward the call:public override Type? GetNullableUnderlyingType() => _innerType.GetNullableUnderlyingType();
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.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fields✅ Done
Activity
AaronRobinsonMSFT commentedon Apr 28, 2026
/cc @jkotas @MichalStrehovsky
Add `Type.GetNullableUnderlyingType()` virtual API (#126905)
Add `Type.GetNullableUnderlyingType()` virtual API (#126905)