的ILGenerator。这个代码有什么问题

时间:2011-09-21 15:56:53

标签: reflection dynamic ilgenerator

我正在尝试构建一个动态的Property Accessor。想要一些非常快的东西,就像接近调用实际属性一样。不想去反射路线非常慢。所以我选择使用DynamicAssembly并使用ILGenerator注入IL。下面是与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);

所以上面得到第一个参数,它是包含该属性的对象。 methods集合包含嵌套属性(如果有)。对于每个属性,我使用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 个答案:

答案 0 :(得分:4)

以前,如果你有连续属性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. 在将值存储到类型为local的本地之前,您不是装箱值类型。
  3. 可以通过稍微修改逻辑来解决这些问题。还有一些其他可以清理的小细节:

    1. 而不是ceq后跟brtrue,只需使用beq
    2. 执行Stloc_1后跟Ldloc_1而不是仅使用堆栈上的值是没有意义的,因为本地不会在其他任何地方使用。
    3. 结合这些变化,这就是我要做的事情:

      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);
      

      请注意,我们可以删除两个本地,因为我们现在只是复制堆栈上的顶部值,然后再与null进行比较。