-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Description
I am using .NET 9+'s new PersistedAssemblyBuilder to dynamically create a class type that implements a given interface:
public interface IFoo
{
void Method(in Span<byte> bytes); // note the `in` modifier
}So I do the following:
- emit my class type
FooDynamicImplimplementing above interface, using System.Reflection.Emit - bake the type via
TypeBuilder.CreateType - write the dynamic assembly to disk via
PersistedAssemblyBuilder.Save - load it back into the runtime (so I have a runnable type) with
Assembly.LoadFrom - try to retrieve the type with
Assembly.GetType("FooDynamicImpl").
The last step causes the following exception to get thrown:
Unhandled exception.
System.TypeLoadException: Signature of the body and declaration in a method implementation do not match. Type:FooDynamicImpl.
Here's some more details regarding generated IL which I've used to create below repro code.
IL emitted for `IFoo` by a current C# compiler
.class interface public abstract auto ansi beforefieldinit IFoo
{
.method public hidebysig newslot abstract virtual
instance void Method([in] valuetype [System.Runtime]System.Span`1<uint8>& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) bytes) cil managed
{
.param [1]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
}
}
Here's the IL that a current C# compiler would emit for a class implementation of the interface:
public class FooImpl : IFoo
{
public void Method(in Span<byte> bytes)
{
}
}IL emitted for `FooImpl` by a current C# compiler
.class public auto ansi beforefieldinit FooImpl
extends [System.Runtime]System.Object
implements IFoo
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
}
.method public hidebysig instance void
Method([in] valuetype [System.Runtime]System.Span`1<uint8>& bytes) cil managed
{
.param [1]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
.maxstack 8
IL_0000: ret
}
.method private hidebysig newslot virtual final
instance void IFoo.Method([in] valuetype [System.Runtime]System.Span`1<uint8>& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) bytes) cil managed
{
.param [1]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
.override IFoo::Method
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call instance void FooImpl::Method(valuetype [System.Runtime]System.Span`1<uint8>&)
IL_0007: ret
}
}
Reproduction Steps
Run the following program – the TypeLoadException should happen during the last statement.
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
var assemblyName = new AssemblyName("DynamicAssembly");
var assembly = new PersistedAssemblyBuilder(assemblyName, typeof(object).Assembly);
var module = assembly.DefineDynamicModule("DynamicAssembly.dll");
// class FooDynamicImpl : IFoo
var type = module.DefineType("FooDynamicImpl", TypeAttributes.Public | TypeAttributes.Class);
type.AddInterfaceImplementation(typeof(IFoo));
// public FooDynamicImpl() { }
var ctor = type.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
// public void Method(in Span<byte> bytes) { }
var method = type.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.HideBySig);
method.SetSignature(
returnType: typeof(void),
returnTypeRequiredCustomModifiers: [],
returnTypeOptionalCustomModifiers: [],
parameterTypes: [typeof(Span<byte>).MakeByRefType()],
parameterTypeRequiredCustomModifiers: [[]],
parameterTypeOptionalCustomModifiers: [[]]);
var methodParameter = method.DefineParameter(1, ParameterAttributes.In, "bytes");
methodParameter.SetCustomAttribute(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, []);
var methodIL = method.GetILGenerator();
methodIL.Emit(OpCodes.Ret);
// void IFoo.Method(in Span<byte> bytes) => Method(bytes)
var fooMethod = type.DefineMethod("IFoo.Method", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual);
type.DefineMethodOverride(fooMethod, typeof(IFoo).GetMethod("Method")!);
fooMethod.SetSignature(
returnType: typeof(void),
returnTypeRequiredCustomModifiers: [],
returnTypeOptionalCustomModifiers: [],
parameterTypes: [typeof(Span<byte>).MakeByRefType()],
parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)]],
parameterTypeOptionalCustomModifiers: [[]]);
var fooMethodParameter = fooMethod.DefineParameter(1, ParameterAttributes.In, "bytes");
fooMethodParameter.SetCustomAttribute(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, []);
var fooMethodIL = fooMethod.GetILGenerator();
fooMethodIL.Emit(OpCodes.Ldarg_0);
fooMethodIL.Emit(OpCodes.Ldarg_1);
fooMethodIL.Emit(OpCodes.Call, method);
fooMethodIL.Emit(OpCodes.Ret);
// Bake type & save dynamic assembly to disk
_ = type.CreateType();
var outputFilePath = Path.GetFullPath("DynamicAssembly.dll");
assembly.Save(outputFilePath);
// load saved assembly back into the runtime and get `FooDynamicImpl` type:
var runnableAssembly = Assembly.LoadFrom(outputFilePath);
var runnableType = runnableAssembly.GetType("FooDynamicImpl"); // <= TypeLoadException happens here
public interface IFoo
{
void Method(in Span<byte> bytes);
}I've inspected the saved assembly with ILDASM, here's the IL for the emitted FooDynamicImpl class type:
IL emitted for `FooDynamicImpl` by the above code:
.class public auto ansi FooDynamicImpl
extends [System.Private.CoreLib]System.Object
implements [ConsoleApp1]IFoo
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
IL_0006: ret
}
.method public hidebysig instance void
Method([in] valuetype [System.Private.CoreLib]System.Span`1<uint8>& bytes) cil managed
{
.param [1]
.custom instance void [System.Private.CoreLib]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor()
.maxstack 8
IL_0000: ret
}
.method private hidebysig newslot virtual final
instance void IFoo.Method([in] valuetype [System.Private.CoreLib]System.Span`1<uint8>& modreq([System.Private.CoreLib]System.Runtime.InteropServices.InAttribute) bytes) cil managed
{
.param [1]
.custom instance void [System.Private.CoreLib]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor()
.override [ConsoleApp1]IFoo::Method
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call instance void FooDynamicImpl::Method(valuetype [System.Private.CoreLib]System.Span`1<uint8>&)
IL_0007: ret
}
}
There are only minor differences to a C# compiler-generated version of an equivalent type (those are: 1. missing custom attribute init data, and 2. type references using [System.Private.CoreLib] instead of [System.Runtime]) but those differences do not appear to be relevant – see the described workaround below.
Expected behavior
I'd expect the program to succeed without error (and the retrieved Type to be instantiable & usable).
Actual behavior
The program throws the exception initially mentioned:
Unhandled exception.
System.TypeLoadException: Signature of the body and declaration in a method implementation do not match. Type:FooDynamicImpl.
Regression?
The described problem does not happen when emitting code using a regular AssemblyBuilder (and thus skipping the assembly save/load roundtrip). It only happens with PersistedAssemblyBuilder, AFAIK on all .NET versions that support it (9+).
Known Workarounds
The issue only appears to happen in the presence of an in parameter. If you replace it with a ref parameter, the exception vanishes:
-
In the
IFoo.Methoddeclaration, replaceinwithref:public interface IFoo { - void Method(in Span<byte> bytes); + void Method(ref Span<byte> bytes); } -
In the call to
fooMethod.SetSignature, change this line of code:fooMethod.SetSignature( returnType: typeof(void), returnTypeRequiredCustomModifiers: [], returnTypeOptionalCustomModifiers: [], parameterTypes: [typeof(Span<byte>).MakeByRefType()], - parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)]], + parameterTypeRequiredCustomModifiers: [[]], parameterTypeOptionalCustomModifiers: [[]]);
Configuration
- .NET versions tested: both 9 and 10 (
PersistedAssemblyBuilderrequires at least .NET 9) - OS: Windows 11 Pro
- Architecture: x64 (CPU: 12th Gen Intel Core i7-1265U)
- I haven't tried other OSes, architectures, nor Blazor
Other information
I think we can also ignore the fact that a byref-like type Span<byte> is involved in my repro. The same exception would occur with a byte parameter.
Possibly related to signature handling of modreqs.