Skip to content

System.Reflection.Context: types mapped by CustomReflectionContext throw NotSupportedException from IsByRefLike and three other Type members #133816

Description

@caraioniurie47

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>, int, List<int>, 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:

using System.Reflection;
using System.Reflection.Context;

var context = new MyContext();
Type mappedSpan = context.MapType(typeof(Span<int>).GetTypeInfo());
Type mappedEnum = context.MapType(typeof(DayOfWeek).GetTypeInfo());

Show("typeof(Span<int>).IsByRefLike", () => typeof(Span<int>).IsByRefLike);
Show("mapped Span<int>.IsByRefLike", () => mappedSpan.IsByRefLike);
Show("mapped DayOfWeek.IsByRefLike", () => mappedEnum.IsByRefLike);
Show("mapped DayOfWeek.GetEnumValuesAsUnderlyingType()", () => mappedEnum.GetEnumValuesAsUnderlyingType().Length);
Show("mapped Span<int>.UnderlyingSystemType.IsByRefLike", () => mappedSpan.UnderlyingSystemType.IsByRefLike);

static void Show(string label, Func<object> 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<int>.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<int>).IsByRefLike -> True
mapped Span<int>.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<int>.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions