C# Reflection Emit Call с параметром

Я хочу вызвать функцию с параметром с помощью reflection.emit API. Ниже то, что у меня есть на данный момент. Но когда я запускаю его, он выдает следующее исключение: System.InvalidProgramException : Common Language Runtime detected an invalid program. Итак, мой вопрос: что у меня не так в моем фрагменте кода ниже? Кто-нибудь может мне помочь?

public class Test
{

    public void test()
    {
        Func<int, long> realSquareFunc = (val) => val * val;
        Type[] methodArgs = { typeof(int) };

        DynamicMethod squareIt = new DynamicMethod(
            "SquareIt",
            typeof(long),
            methodArgs,
            typeof(Test).Module)
        ;

        ILGenerator il = squareIt.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); // Save parameter on stack
        il.Emit(OpCodes.Call, realSquareFunc.Method); // Call function with input as parameter
        il.Emit(OpCodes.Ret); // Return value from function call before

        var myMethod = (Func<int, long>)squareIt.CreateDelegate(realSquareFunc.GetType());
        var result = myMethod.Invoke(4); // Should be 16 (4*4)
    }

}

person Dragonblf    schedule 17.05.2020    source источник


Ответы (1)


Ваш код будет работать как есть, если вызываемый метод будет статическим:

public static long RealSquare(int val) => val * val;

public void test()
{
    Func<int, long> realSquareFunc = RealSquare;
    // ...

Однако на самом деле лямбда realSquareFunc = (val) => val * val компилируется как метод экземпляра скрытого класса. Чтобы вызвать метод экземпляра, экземпляр должен быть сначала помещен в стек до аргументов метода. Вызовы методов экземпляра также обычно используют код операции Callvirt (независимо от того, являются ли они виртуальными, потому что этот код операции выполняет проверку нулевых ссылок):

public class Test
{
    public long RealSquare(int val) => val * val;

    public void test()
    {
        Func<int, long> realSquareFunc = RealSquare;
        // pass the instance we want to call the method on in as well
        Type[] methodArgs = { typeof(Test), typeof(int) };

        DynamicMethod squareIt = new DynamicMethod(
            "SquareIt",
            typeof(long),
            methodArgs,
            typeof(Test).Module)
        ;

        ILGenerator il = squareIt.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); // Push the target instance onto stack      
        il.Emit(OpCodes.Ldarg_1); // Save parameter on stack
        il.Emit(OpCodes.Callvirt, realSquareFunc.Method); // Call function with input as parameter
        il.Emit(OpCodes.Ret); // Return value from function call before

        var myMethod = (Func<Test, int, long>)squareIt.CreateDelegate(typeof(Func<Test, int, long>));
        var result = myMethod.Invoke(this, 4); // Should be 16 (4*4)
    }  
}

Вызов целевого метода лямбды напрямую более сложен из-за задействованного класса, сгенерированного компилятором, но если вы хотите вызвать делегат в целом, это работает следующим образом:

public class Test
{        
    public void test()
    {
        Func<int, long> realSquareFunc = (val) => val * val;
        // pass the delegate we want to call into the method
        Type[] methodArgs = { realSquareFunc.GetType(), typeof(int) };

        DynamicMethod squareIt = new DynamicMethod(
            "SquareIt",
            typeof(long),
            methodArgs,
            typeof(Test).Module)
        ;

        ILGenerator il = squareIt.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); // Push the delegate onto stack        
        il.Emit(OpCodes.Ldarg_1); // Save parameter on stack
        il.Emit(OpCodes.Callvirt, realSquareFunc.GetType().GetMethod("Invoke")); // Invoke delegate
        il.Emit(OpCodes.Ret); // Return value from function call before

        var myMethod = (Func<Func<int, long>, int, long>)squareIt
            .CreateDelegate(typeof(Func<Func<int, long>, int, long>));
        var result = myMethod.Invoke(realSquareFunc, 4); // Should be 16 (4*4)
    }  
}
person kalimag    schedule 17.05.2020