Исключение InvalidProgramException: Недопустимый код IL возникает при выдаче кода IL с возвращаемым типом, не являющимся недействительным

#c# #.net #delegates #reflection.emit #ilgenerator

Вопрос:

Я пытаюсь сократить время вызова метода отражения, создав динамический метод, выдав код IL с его генератором IL, а затем создав делегат с помощью метода CreateDelegate. До сих пор время вызова значительно сокращается, даже несмотря на то, что новый вызов метода все еще использует в качестве параметров (параметры объекта callObject, object []). Проблема возникает в сгенерированном коде IL, когда тип возвращаемого значения отличается от void. Я ничего не знаю о MSIL и провел довольно много исследований, но ничего не нашел. Вот код, который работает:

 private void Start()
{
    var a = new A();
    var method = typeof(A).GetMethod("Add");

    Type[] paramsTypes = { typeof(object[]) };
    Type[] allTypes = { typeof(object), typeof(object[]) };
    Type returnType = typeof(void);

    var dm = new DynamicMethod("Hi", returnType, allTypes);

    var il = dm.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, method, paramsTypes);
    il.Emit(OpCodes.Ret);

    var del4 = (Action<object, object[]>)dm.CreateDelegate(
        typeof(Action<object, object[]>));

    var time = DateTime.Now;

    for (int i = 0; i < 20000000; i  )
    {
        //a.Add(); 132 ms

        //method.Invoke(a, null);// 25 sec

        del4(a, null); // 200 ms
    }

    var ts = DateTime.Now - time;

    Debug.Log($"{ts.Seconds}:{ts.Milliseconds}");
}

public class A
{
    public int a = 0;

    public void Add() => a  ;
}
 

Результирующее время (измеряется только время вызова) намного быстрее по сравнению с обычным MethodInfo.Взывать. Однако при следующих изменениях генератор IL выдает ошибку:

 private void Start()
{
    var a = new A();
    var method = typeof(A).GetMethod("Add");

    Type[] paramsTypes = { typeof(object[]) };
    Type[] allTypes = { typeof(object), typeof(object[]) };
    Type returnType = typeof(object);

    var dm = new DynamicMethod("Hi", returnType, allTypes);

    var il = dm.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, method, paramsTypes);
    il.Emit(OpCodes.Ret);

    var del4 = (Func<object, object[], object>)dm.CreateDelegate(
        typeof(Func<object, object[], object>));

    var time = DateTime.Now;

    for (int i = 0; i < 20000000; i  )
    {
        //a.Add(); 132 ms

        //method.Invoke(a, null);// 25 sec

        del4(a, null); // 200 ms
    }

    var ts = DateTime.Now - time;

    Debug.Log($"{ts.Seconds}:{ts.Milliseconds}");
}

public class A
{
    public int a = 0;

    public int Add() => a  ;
}
 

Когда ожидаемый тип возвращаемого значения отличается от void, возникает следующее исключение:

 InvalidProgramException: Invalid IL code in (wrapper dynamic-method) object:Hi (object,object[]): IL_0006: ret
 

Кто-нибудь знает, как решить эту проблему?

Изменить: Добавлен бокс перед возвратом кода операции:

     il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, method, paramsTypes);
    il.Emit(OpCodes.Box);
    il.Emit(OpCodes.Ret);
 

Затем выбрасывается следующее:

 VerificationException: Error in System.Object:(wrapper dynamic-method) object:Hi (object,object[]) Invalid instruction 8c
 

Комментарии:

1. Вы уверены, что это не работает ни для какого другого типа возвращаемого значения, кроме void ? Тип возврата int должен работать. Для object этого вам нужно будет выдать другую box инструкцию. Посмотрите, как это сделать. Тем не менее, я даже не могу получить версию, которая, как вы утверждаете, работает, для работы на моей машине. Может быть, это потому, что я использую .NET 5…

2. @Sweeper Я использовал именно тип int, и он не работает. Когда я переключил тип возврата динамического метода на int, а типы возврата функций-на int, это сработало. Но так как я хочу что-то общее, мне нужно, чтобы возвращался тип объекта. Проблема, вероятно, в боксе, как вы и сказали. Но я добавил бокс перед возвращением, и это не сработало. И этот работает в unity со стандартом .Net 4.x , который, как я полагаю, использует c# 7.3.

3. @Уборочная машина Готова..

4. Ммм… Это не то, как вы выдаете box инструкцию. Я включил ссылку в свой первый комментарий к документации, сообщив вам, какую перегрузку Emit вы должны использовать, и вы, похоже, вообще ее не читали… Вы также должны передать тип, который вы боксируете typeof(int) , или, в более общем плане, method.ReturnType .

5. Для Ret требуется значение в стеке, которое возвращается в методе, не являющемся пустым. Если ваш метод объявляет о возврате чего-либо, но верификатор видит пустой стек при возврате, вы получаете исключение