[API Proposal]: Add API to support emitting constructed generic types from varying reflection stacks.
- 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
Assessment
This issue has not been assessed yet.