ILGenerator.Что не так с этим Кодексом - PullRequest
1 голос
/ 21 сентября 2011

Я пытаюсь создать динамический Accessor Access. Хотите что-то, что по-настоящему быстро, как близко к названию на самом деле собственности. Не хочу идти по пути Отражения, так как он очень медленный. Поэтому я решил использовать DynamicAssembly и внедрить IL с помощью ILGenerator. Ниже приведен код, связанный с ILGenerator, который, кажется, работает

        Label nulllabel = getIL.DefineLabel();
        Label returnlabel = getIL.DefineLabel();
        //_type = targetGetMethod.ReturnType;
        if (methods.Count > 0)
        {
            getIL.DeclareLocal(typeof(object));
            getIL.DeclareLocal(typeof(bool));

            getIL.Emit(OpCodes.Ldarg_1); //Load the first argument

            //(target object)

            //Cast to the source type

            getIL.Emit(OpCodes.Castclass, this.mTargetType);
            //Get the property value

            foreach (var methodInfo in methods)
            {
                getIL.EmitCall(OpCodes.Call, methodInfo, null);

                if (methodInfo.ReturnType.IsValueType)
                {
                    getIL.Emit(OpCodes.Box, methodInfo.ReturnType);
                    //Box if necessary
                }
            }

            getIL.Emit(OpCodes.Stloc_0); //Store it
            getIL.Emit(OpCodes.Br_S,returnlabel);

            getIL.MarkLabel(nulllabel);
            getIL.Emit(OpCodes.Ldnull);
            getIL.Emit(OpCodes.Stloc_0);

            getIL.MarkLabel(returnlabel);
            getIL.Emit(OpCodes.Ldloc_0);
        }
        else
        {
            getIL.ThrowException(typeof(MissingMethodException));
        }
        getIL.Emit(OpCodes.Ret);

Итак, выше получите первый аргумент, который является объектом, который содержит свойство. коллекция методов содержит вложенное свойство, если оно есть. для каждого свойства я использую EmitCall, который помещает значение в стек, а затем я пытаюсь его упаковать. Это работает как шарм.

Единственная проблема в том, что если у вас есть свойство типа Order.Instrument.Symbol.Name и предполагается, что объект Instrument имеет значение null Затем код сгенерирует исключение нулевого объекта.

Так вот что я сделал, я ввел нулевую проверку

            foreach (var methodInfo in methods)
            {
                getIL.EmitCall(OpCodes.Call, methodInfo, null);

                getIL.Emit(OpCodes.Stloc_0);
                getIL.Emit(OpCodes.Ldloc_0);

                getIL.Emit(OpCodes.Ldnull);
                getIL.Emit(OpCodes.Ceq);
                getIL.Emit(OpCodes.Stloc_1);
                getIL.Emit(OpCodes.Ldloc_1);
                getIL.Emit(OpCodes.Brtrue_S, nulllabel);
                getIL.Emit(OpCodes.Ldloc_0);

                if (methodInfo.ReturnType.IsValueType)
                {
                    getIL.Emit(OpCodes.Box, methodInfo.ReturnType);
                    //Box if necessary
                }
            }

Теперь этот код не работает, говоря, что объект / память повреждены и т. Д. Так что же не так с этим кодом. Я что-то здесь упускаю.

Заранее спасибо.

1 Ответ

4 голосов
/ 27 сентября 2011

Ранее, если бы у вас были последовательные свойства P, возвращающие строку, а затем Q, возвращающие int, вы получили бы что-то вроде этого:

...   
call P // returns string
call Q // requires a string on the stack, returns an int
box
...

Теперь у вас есть что-то вроде этого:

...
call P // returns string
store  // stores to object
...    // load, compare to null, etc.
load   // loads an *object*
call Q // requires a *string* on the stack
store  // stores to object *without boxing*
...

Итак, я вижу две очевидные проблемы:

  1. Вы вызываете методы таким образом, что известно, что целью является только объект, а не конкретный тип, имеющий этот метод.
  2. Вы не упаковываете типы значений перед сохранением их в локальном объекте типа.

Это можно решить, немного переработав логику. Есть также несколько других мелких деталей, которые вы можете очистить:

  1. Вместо ceq, за которым следует brtrue, просто используйте beq.
  2. Нет смысла делать Stloc_1 с последующим Ldloc_1, а не просто использовать значение в стеке, так как этот локальный больше нигде не используется.

Включая эти изменения, вот что я бы сделал:

Type finalType = null;

foreach (var methodInfo in methods)
{
    finalType = methodInfo.ReturnType;

    getIL.EmitCall(OpCodes.Call, methodInfo, null);
    if (!finalType.IsValueType)
    {
        getIL.Emit(OpCodes.Dup);
        getIL.Emit(OpCodes.Ldnull);
        getIL.Emit(OpCodes.Beq_S, nulllabel);
    }
}

if (finalType.IsValueType)
{
    getIL.Emit(OpCodes.Box, methodInfo.ReturnType);
    //Box if necessary
}

getIL.Emit(OpCodes.Br_S, returnLabel);

getIL.MarkLabel(nulllabel);
getIL.Emit(OpCodes.Pop);    
getIL.Emit(OpCodes.Ldnull);

getIL.MarkLabel(returnlabel);

Обратите внимание, что мы можем избавиться от обоих локальных элементов, поскольку теперь мы просто дублируем верхнее значение в стеке перед сравнением с нулем.

...