2014-02-07 106 views
1

我必須定義一個方法Reflection.Emit,這個方法相當複雜,因爲我必須對字段執行for循環,並且有一個break和return條件。我的方法,我想與反思重建看起來像這樣在常規代碼:使用for循環和條件語句定義方法

override int GetKeyImpl(Type obj0) 
{ 
    int answer = -1; 
    for(int i = 0; i < knownTypes.length; i++){ 
      if(knowntypes[i] == obj0){ 
       answer = i; 
       break; 
      } 
    } 
    return answer; 
} 

我的想法來解決這個問題是產生與反射的方法是將呼叫重定向到我原來的方法並返回int

我需要知道如何做一個for循環,並打破OpCodes重新創建方法,同時對類內的數組進行條件檢查。我已經搜索了教程,但沒有發現比添加兩個整數更遠的地方。

編輯:忘了提起它,我正在使用IKVM.Reflection和knownTypes是Type []的數組。即時寫作的方法是重寫抽象的方法。

+2

如果您可以將其重構爲靜態方法(其中'knowntypes是一個參數或靜態成員),您可以使用LINQ表達式樹構建方法體,然後使用'Expression.CompileToMethod()'編譯它到一個'MethodBuidler'。這比手動發射IL更容易。 –

回答

2

這應該重現您所指定的方法:

TypeBuilder type = /* ... */; 
FieldInfo knownFields = /* ... */; 

// Finding dependencies via reflection 
var baseMethod = type.BaseType.GetMethod(
    "GetKeyImpl", 
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 

var typeEqualsOperator = typeof(Type).GetMethod(
    "op_Equality", 
    BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, 
    null, 
    new[] { typeof(Type), typeof(Type) }, 
    null); 

// Declaring the method 
var getKeyImpl = type.DefineMethod(
    baseMethod.Name, 
    baseMethod.Attributes & ~(MethodAttributes.Abstract | 
           MethodAttributes.NewSlot)); 

// Setting return type 
getKeyImpl.SetReturnType(typeof(int)); 

// Adding parameters 
getKeyImpl.SetParameters(typeof(Type)); 
getKeyImpl.DefineParameter(1, ParameterAttributes.None, "obj0"); 

// Override the base method 
type.DefineMethodOverride(getKeyImpl, baseMethod); 

var il = getKeyImpl.GetILGenerator(); 

// Preparing locals 
var answer = il.DeclareLocal(typeof(int)); 
var i = il.DeclareLocal(typeof(int)); 

// Preparing labels 
var loopCondition = il.DefineLabel(); 
var loopIterator = il.DefineLabel(); 
var returnLabel = il.DefineLabel(); 
var loopBody = il.DefineLabel(); 

// Writing body 

// answer = -1 
il.Emit(OpCodes.Ldc_I4_M1); 
il.Emit(OpCodes.Stloc, answer); 

// i = 0 
il.Emit(OpCodes.Ldc_I4_0); 
il.Emit(OpCodes.Stloc, i); 

// jump to loop condition 
il.Emit(OpCodes.Br_S, loopCondition); 

// begin loop body 
il.MarkLabel(loopBody); 

// if (obj0 != knownTypes[i]) continue 
il.Emit(OpCodes.Ldarg_0); // omit if 'knownTypes' is static 
il.Emit(OpCodes.Ldfld, knownTypes); // use 'Ldsfld' if 'knownTypes' is static 
il.Emit(OpCodes.Ldloc, i); 
il.Emit(OpCodes.Ldelem_Ref); 
il.Emit(OpCodes.Ldarg_1); // use 'Ldarg_0' if 'knownTypes' is static 
il.Emit(OpCodes.Call, typeEqualsOperator); 
il.Emit(OpCodes.Brfalse_S, loopIterator); 

// answer = i; jump to return 
il.Emit(OpCodes.Ldloc, i); 
il.Emit(OpCodes.Stloc, answer); 
il.Emit(OpCodes.Br_S, returnLabel); 

// begin loop iterator 
il.MarkLabel(loopIterator); 

// i = i + 1 
il.Emit(OpCodes.Ldloc, i); 
il.Emit(OpCodes.Ldc_I4_1); 
il.Emit(OpCodes.Add); 
il.Emit(OpCodes.Stloc, i); 

// begin loop condition 
il.MarkLabel(loopCondition); 

// if (i < knownTypes.Length) jump to loop body 
il.Emit(OpCodes.Ldloc, i); 
il.Emit(OpCodes.Ldarg_0); // omit if 'knownTypes' is static 
il.Emit(OpCodes.Ldfld, knownTypes); // use 'Ldsfld' if 'knownTypes' is static 
il.Emit(OpCodes.Ldlen); 
il.Emit(OpCodes.Conv_I4); 
il.Emit(OpCodes.Blt_S, loopBody); 

// return answer 
il.MarkLabel(returnLabel); 
il.Emit(OpCodes.Ldloc, answer); 
il.Emit(OpCodes.Ret); 

// Finished! 

的反編譯的結果正如所料:

override int GetKeyImpl(Type obj0) 
{ 
    for (int i = 0; i < this.knownTypes.Length; i++) 
    { 
     if (this.knownTypes[i] == obj0) 
      return i; 
    } 
    return -1; 
} 

如果你有機會到.NET反射,有Reflection.Emit Language Add-In您可能會感興趣。或者,用C#代碼編寫一個原型,然後通過反彙編程序運行它以查看原始IL。

如果可以使方法static(並接受knownTypes作爲參數或使其成爲static字段),那麼您可以使用LINQ表達式樹組成方法體。不幸的是,你不能用這種技術來編寫實例方法體;他們必須是static。例如:

var method = typeBuilder.DefineMethod(
    "GetKeyImpl", 
    MethodAttributes.Private | 
    MethodAttributes.Static | 
    MethodAttributes.HideBySig); 

var type = E.Parameter(typeof(Type), "type"); 
var knownTypes = E.Parameter(typeof(Type[]), "knownTypes"); 

var answer = E.Variable(typeof(int), "answer"); 
var i = E.Variable(typeof(int), "i"); 

var breakTarget = E.Label("breakTarget"); 
var continueTarget = E.Label("continueTarget"); 
var returnTarget = E.Label(typeof(int), "returnTarget"); 

var forLoop = E.Block(
    new[] { i }, 
    E.Assign(i, E.Constant(0)), 
    E.Loop(
     E.Block(
      E.IfThen(
       E.GreaterThanOrEqual(i, E.ArrayLength(knownTypes)), 
       E.Break(breakTarget)), 
      E.IfThen(
       E.Equal(E.ArrayIndex(knownTypes, i), type), 
       E.Return(returnTarget, i)), 
      E.Label(continueTarget), 
      E.PreIncrementAssign(i))), 
    E.Label(breakTarget)); 

var body = E.Lambda<Func<Type, Type[], int>>(
    E.Block(
     new[] { answer }, 
     E.Assign(answer, E.Constant(-1)), 
     forLoop, 
     E.Label(returnTarget, answer)), 
    type, 
    knownTypes); 

body.CompileToMethod(method); 

return method; 

上面的例子接受knownTypes作爲第二個參數。重構從靜態字段讀取將是直接的。經反編譯的結果,又是如期望的那樣。

private static int GetKeyImpl(Type type, Type[] knownTypes) 
{ 
    for (int i = 0; i < knownTypes.Length; i++) 
    { 
     if (knownTypes[i] == type) 
      return i; 
    } 
    return -1; 
} 
+0

你的第一個答案似乎接近我尋找的解決方案。我差不多完成了,但是因爲我正在使用IKVM.Reflection操作typeEqualsOperator不起作用。需要自己弄清楚這一點。感謝您的支持。 –

+0

我不熟悉那個庫,但會'object.Equals(object,object)'或'object.ReferenceEquals(object,object)'工作嗎?如果是這樣,您只需將該任務更新爲'typeEqualsOperator',並且不需要進行其他更改。 –

+0

已更新我的回答,以顯示如何正確聲明覆蓋。 –

0

的easist的工作方式如何產生IL一種方法是創建一個有你的方法在一個簡單的控制檯應用程序然後建立並運行ILDasm反對它來查看構成該方法所必需的IL指令。

一旦你看到指令,編寫代碼以發出必要的代碼不應該太困難。