0

I am trying to dynamically build a type with a method that calls into an external delegate by using System.Reflection.Emit. However when I try to call this method, my program crashes with the exception in the title at the method call. Here's my code so far:

private static void TestMethodReal() => Console.Out.WriteLine("Inside TestMethod");

// In Main()
var method = typeof(Program).GetMethod(nameof(TestMethodReal), BindingFlags.Static | BindingFlags.NonPublic)!;

var builder = MyTypeBuilder.GetTypeBuilder("TestType");
var testMethod = builder.DefineMethod("TestMethod", MethodAttributes.Public, typeof(void), Type.EmptyTypes);

var generator = testMethod.GetILGenerator();
generator.EmitCall(OpCodes.Callvirt, method, null);
generator.Emit(OpCodes.Ret);

dynamic inst = Activator.CreateInstance(builder.CreateType()!)!;
inst.TestMethod(); // <--- Exception is thrown here

The MyTypeBuilder class and GetTypeBuilder method is from this answer, slightly modified to accept a parameter for the type's name.

This program is supposed to create a new dynamic class with a method called TestMethod that calls the actual TestMethodReal method, instantiate the class, and call the method.

What am I missing?

Lázár Zsolt
  • 685
  • 8
  • 29
  • This is strange `BindingFlags.Static | BindingFlags.NonPublic)!;`. What is that `!` for?. And again in the line just before your error `Activator.CreateInstance(builder.CreateType()!)!;` – Cleptus Dec 29 '21 at 16:04
  • It's a somewhat annoying feature of one of the new C# versions (9.0 or 10.0 I believe). `GetMethod` returns a nullable `MethodInfo?` type (note the '?' at the end). The `!` basically ensures that the return value is not null, otherwise my IDE will complain about it and draw an ugly yellow squiggly line underneath it. Same with `CreateType` and `CreateInstance`, returning nullable `Type?` and `object?` types respectively. – Lázár Zsolt Dec 29 '21 at 16:10
  • 1
    Call the target method directly instead of requesting virtual dispatch: `generator.EmitCall(OpCodes.Call, method, Types.EmptyTypes)` - you don't need the runtime to resolve `TestMethodReal` for you since you already have an exact reference to it via `method` :) – Mathias R. Jessen Dec 29 '21 at 16:13
  • Are you sure you are running on .NET 6 / .NET Core cause [`AppDomain.DefineDynamicAssembly`](https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.definedynamicassembly?view=netframework-4.8) used in linked answer is .NET Framework only. – Guru Stron Dec 29 '21 at 16:16
  • @GuruStron I forgot to mention this, but the .NET Core replacement of that method is [AssemblyBuilder.DefineDynamicAssembly](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilder.definedynamicassembly?view=net-6.0), which I also replaced in the linked `GetTypeBuilder` method. – Lázár Zsolt Dec 29 '21 at 16:20
  • @MathiasR.Jessen Thank you, this seems to have solved my problem for now. If you move your comment to an answer, I'll upvote it :) – Lázár Zsolt Dec 29 '21 at 16:25
  • Have you considered giving up dynamic type building and creating a direct delegate to the method `(Action)method.CreateDelegate(typeof(Action))` – Charlieface Dec 29 '21 at 22:18

1 Answers1

2

You're using the wrong dispatch mechanism!

OpCodes.Callvirt is for virtual method calls, eg. overridable instance methods, the resolution of which needs to be deferred until runtime.

For static method invocation you'll want a plain old OpCodes.Call instruction instead:

generator.EmitCall(OpCodes.Call, method, Types.EmptyTypes);
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206