dotnet / dotnet/runtime

[API Proposal]: Add API to support emitting constructed generic types from varying reflection stacks.

Open
#126,182 4 comments 1 reaction 0 assignees View on GitHub
api-suggestion area-System.Reflection.Emit
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

#125612 exposed a feature gap in `PersistedAssemblyBuilder` when used alongside `MetadataLoadContext` [as suggested](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-reflection-emit-persistedassemblybuilder). Because a generic type from an MLC [cannot be specialized](https://github.com/dotnet/runtime/blob/67495bce3f413302a4f47820c03d81b67897da7c/src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoDefinitionType.cs#L114-L115) with type parameters outside the MLC, type instantiations like `MyTypeFromMLC` where `T` is a `TypeBuilder` of a just-created type, are not possible at least in some scenarios.

Both [runtime reflection](https://github.com/dotnet/runtime/blob/67495bce3f413302a4f47820c03d81b67897da7c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L3637-L3646) and [Reflection.Emit](https://github.com/dotnet/runtime/blob/67495bce3f413302a4f47820c03d81b67897da7c/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs#L317-L322) support constructing generic types with foreign `Type` instances, which makes me believe that MLC should too. However, because MLC is defined outside of CoreLib or the shared framework, this cannot happen without new APIs. `TypeBuilder` [already has APIs](https://github.com/dotnet/runtime/blob/67495bce3f413302a4f47820c03d81b67897da7c/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs#L329-L442) to construct member references of constructed generic types, but [not a public API](https://github.com/dotnet/runtime/blob/67495bce3f413302a4f47820c03d81b67897da7c/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilderInstantiation.cs#L31-L46) to construct these generic types (runtime reflection and Reflection.Emit got away with this by using regular `Type.MakeGenericType`, because they are defined in CoreLib). I am proposing to add one.

### API Proposal

```diff
namespace System.Reflection.Emit;

public class TypeBuilder
{
+ static Type MakeGenericType(Type genericTypeDefinition, Type[] typeArguments);
}
```

### API Usage

```csharp
MetadataLoadContext mlc;
ModuleBuilder mb;

Type baseClassT = mlc.LoadAssemblyFromName("MyDependency").GetType("MyDependency.BaseClass`1", true)!;
TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public);
Type baseClassOfMyType = TypeBuilder.MakeGenericType(baseClassT, [tb]);
mb.SetParent(baseClassOfMyType);
```

The above is one possible use, but `MetadataLoadContext`'s `MakeGenericType` will be updated to make use of this new API, such that we can define `baseClassOfMyType` with `baseClassT.MakeGenericType(tb)`. On earlier frameworks, MLC will throw a `PlatformNotSupportedException`.

### Alternative Designs

We could have MLC define its own `ConstructedGenericType` subclass of `Type`, but [some places](https://github.com/dotnet/runtime/blob/67495bce3f413302a4f47820c03d81b67897da7c/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs#L334-L337) in `Reflection.Emit` expect instances of `TypeBuilder` or `TypeBuilderInstantiation`, the latter of which is internal and needs the proposed API for user code to create. This restriction could be relaxed, but I'm not sure if it's a good idea.

We could also only add this API and instruct people to use it, without affecting MLC's "closed universe"ness, but I'm not sure I like this idea, given the behavior of other reflection stacks, and the migration effort for developers.

### Risks

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.