System.Reflection.Context: types mapped by `CustomReflectionContext` throw `NotSupportedException` from `IsByRefLike` and three other `Type` members
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
A `Type` mapped by a `CustomReflectionContext` throws `NotSupportedException: Derived classes must provide an implementation.` from `IsByRefLike`, `GetEnumValuesAsUnderlyingType()`, `GetNullableUnderlyingType()` and `MakeFunctionPointerType(...)`, whatever type it wraps (`IsByRefLike` checked for `Span`, `int`, `List`, `DayOfWeek` and `int?`).
Those four are `Type` virtuals whose base implementation throws `NotSupportedException(SR.NotSupported_SubclassOverride)` (`Type.cs:66`, `:652`, `:615`, `:766` at `40caa57`). The mapped types are `CustomType : ProjectingType : DelegatingType`, and none of the three overrides them. Of the ten members in `Type.cs` with that throwing base, `DelegatingType` overrides the other six (`GetArrayRank`, `GetGenericTypeDefinition`, `GetGenericArguments`, `GetMember`, `GetInterfaceMap`, `MakeGenericType`), each forwarding to the wrapped type.
The three newer members were added to `Type` without a System.Reflection.Context change: #73057 (`GetEnumValuesAsUnderlyingType`) and #126905 (`GetNullableUnderlyingType`) updated System.Reflection.MetadataLoadContext but not System.Reflection.Context, and #123819 (`MakeFunctionPointerType`) updated neither.
Found while working on #91532, which is the same exception from Reflection.Emit types.
### Reproduction Steps
`net10.0` console app referencing the `System.Reflection.Context` package:
```csharp
using System.Reflection;
using System.Reflection.Context;
var context = new MyContext();
Type mappedSpan = context.MapType(typeof(Span).GetTypeInfo());
Type mappedEnum = context.MapType(typeof(DayOfWeek).GetTypeInfo());
Show("typeof(Span).IsByRefLike", () => typeof(Span).IsByRefLike);
Show("mapped Span.IsByRefLike", () => mappedSpan.IsByRefLike);
Show("mapped DayOfWeek.IsByRefLike", () => mappedEnum.IsByRefLike);
Show("mapped DayOfWeek.GetEnumValuesAsUnderlyingType()", () => mappedEnum.GetEnumValuesAsUnderlyingType().Length);
Show("mapped Span.UnderlyingSystemType.IsByRefLike", () => mappedSpan.UnderlyingSystemType.IsByRefLike);
static void Show(string label, Func f)
{
try { Console.WriteLine($"{label} -> {f()}"); }
catch (Exception e) { Console.WriteLine($"{label} -> {e.GetType()}: {e.Message}"); }
}
class MyContext : CustomReflectionContext { }
```
### Expected behavior
A mapped type answers as the type it wraps: `mapped Span.IsByRefLike` is `True`, `mapped DayOfWeek.IsByRefLike` is `False`, and `GetEnumValuesAsUnderlyingType()` returns the seven values.
### Actual behavior
Identical output on .NET 10.0.12 and on a local `main` build:
```
typeof(Span).IsByRefLike -> True
mapped Span.IsByRefLike -> System.NotSupportedException: Derived classes must provide an implementation.
mapped DayOfWeek.IsByRefLike -> System.NotSupportedException: Derived classes must provide an implementation.
mapped DayOfWeek.GetEnumValuesAsUnderlyingType() -> System.NotSupportedException: Derived classes must provide an implementation.
mapped Span.UnderlyingSystemType.IsByRefLike -> True
```
The other two members are newer than .NET 10, so they were called through reflection on the `main` build only:
| call | unmapped | mapped |
|---|---|---|
| `typeof(int?).GetNullableUnderlyingType()` | `System.Int32` | `NotSupportedException` |
| `typeof(int).GetNullableUnderlyingType()` | `null` | `NotSupportedException` |
| `typeof(int).MakeFunctionPointerType(null, false)` | `System.Int32()` | `NotSupportedException` |
### Regression?
Not tested on releases before .NET 10.
### Known Workarounds
`UnderlyingSystemType` on a mapped type returns the wrapped runtime type, which answers correctly (last line above). Anything obtained from it is no longer projected by the context.
### Configuration
.NET 10.0.12 with System.Reflection.Context package 10.0.12, Windows 11 x64. Local `main` build at `40caa57` (`clr+libs -rc release -lc release`, reporting 12.0.0-dev), with System.Reflection.Context 11.0.0.0 built from the same tree. The build includes the Reflection.Emit fix for #91532, which does not touch System.Reflection.Context.
### Other information
A fix following the existing overrides: forward the four members to the wrapped type in `DelegatingType`, as `GetArrayRank` does, and in `ProjectingType` project the `Type` results of `GetNullableUnderlyingType` and `MakeFunctionPointerType`, as `GetGenericTypeDefinition` and `MakeGenericType` do. The project also builds these sources for `$(NetCoreAppPrevious)`, `$(NetCoreAppMinimum)` and `netstandard2.1`, so an override of a member one of those lacks needs a target-framework condition.
Searching issues and PRs for `CustomReflectionContext`, and for the exception message, found no existing report.
I'd like to implement this fix and would appreciate being assigned.
> [!NOTE]
> AI-generated, written at my direction and reviewed by me before posting. The repro above was run as written on .NET 10.0.12 and on the local build named under Configuration; a separate probe read `IsByRefLike` on the five mapped types named above on both, and called the two newer members through reflection on the `main` build. File and line references and the three commits' file lists were checked with `git grep` and `git show --stat` at `40caa57`.
Contributor guide
Research direction
Start with the throwing virtual members in Type.cs and the existing forwarding overrides in DelegatingType and ProjectingType. Run the supplied net10.0 reproduction, then verify that the four members delegate to wrapped types and that Type-returning results remain projected, with appropriate target-framework conditions.】【。
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100