#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 требуется значение в стеке, которое возвращается в методе, не являющемся пустым. Если ваш метод объявляет о возврате чего-либо, но верификатор видит пустой стек при возврате, вы получаете исключение