2011-09-21 135 views
1

我正在嘗試構建一個動態屬性訪問器。想要的東西就像接近調用實際屬性一樣快。不想去反射路線,因爲它非常緩慢。所以我選擇使用DynamicAssembly並使用ILGenerator注入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 
       } 
      } 

下面這段代碼符說,對象/內存損壞等,所以究竟是什麼不對的代碼。我在這裏錯過了什麼。

在此先感謝。

回答

4

以前,如果你曾經連續化學出版社返回字符串,那麼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. 您正在調用的方法,目標只知道是一個ob ject,不是具有該方法的特定類型。
  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); 

請注意,我們可以擺脫當地人的,因爲我們現在只是對空比較之前複製堆棧頂部的值。