12

我目前面臨着運營商的新問題。使用下面的代碼,我想要使輸出與在C#中使用if ... else對相同。C#反射:如果... else?

var method = new DynamicMethod("dummy", null, Type.EmptyTypes); 
var g = method.GetILGenerator(); 

g.Emit(OpCodes.Ldstr, "string"); 
g.Emit(OpCodes.Ldstr, "string"); 
g.Emit(OpCodes.Call, typeof(String).GetMethod("op_Equality", new Type[]{typeof(string), typeof(string)})); 
g.Emit(OpCodes.Ldc_I4, 0); 
g.Emit(OpCodes.Ceq); 
g.Emit(OpCodes.Brtrue_S,); 

var action = (Action)method.CreateDelegate(typeof(Action)); 
action(); 

Console.Read(); 

我的問題是:

  1. 我怎樣才能得到一個指令的地址把它作爲分支操作碼的參數?
  2. BRBR_S,BrtrueBrtrue_S,BrfalseBrfalse_S以及類似說明是否有區別?

感謝。

+3

正如其他人所提到的,分支指令的「_S」版本需要1個字節而不是4個字節的偏移量。如果您知道您的分支將始終位於可用範圍內(-128字節至+127字節),那麼您可以通過使用它們來獲得更緊湊的代碼,但是如果您嘗試使用它們跳轉到外部帶有偏移量的標籤這個範圍,創建委託時會引發異常。 – Iridium

回答

8

ILGenerator.ILOffset爲您提供IL流中的當前偏移量,如果這是您想要的。按照goric的建議,您也可以使用DefineLabelMarkLabel

brtrue.sbrtrue之間的唯一區別是brtrue.sbrtrue的簡短版本。 brtrue使用4字節的偏移量,brtrue.s使用1字節的偏移量。這同樣適用於brfalsebrfalse.s(和br/br.s)。

這些不是IL指令的唯一簡短版本,還有其他簡短指令,如加載整數的ldc.i4.0-ldc.i4.8。這些主要用於生成較小的二進制文件,但我認爲不會有很大的差別。

8
  1. 可以使用的DefineLabelMarkLabel相結合的方法來確定 分支的目標位置。爲此,請聲明您需要的標籤 - 例如 equalnotequal。然後,您可以在您的IL中標記標籤應該存在的位置。一旦完成,您可以將分支指令的 目標設置爲此標籤。

    // Define labels 
    Label equal = g.DefineLabel(); 
    Label notEqual = g.DefineLabel(); 
    Label endOfMethod = g.DefineLabel(); 
    // your logic here 
    g.Emit(OpCodes.Brtrue_S, equal); 
    g.MarkLabel(equal); 
    // some code if they are equal 
    g.MarkLabel(notEqual); 
    // some code if they are not equal 
    g.MarkLabel(endOfMethod); // this marks the return point 
    g.Emit(OpCodes.Ret); 
    
  2. BrBrtrue,和Brfalse及其_S 同行之間的差別是跳躍的長度。 _S表示簡短形式;目標指令是來自下一條指令的1字節有符號偏移量。在標準(非短)形式中,目標是由4字節偏移量表示的 。

4

這是新的給我,但顯然有點搜索您可以通過調用

Label targetInstruction = g.DefineLabel(); 

在某些時候事先獲得的地址(例如之前的第一Emit?),然後

g.MarkLabel(targetInstruction); 

之前發射要跳轉到線。然後這個Label是你的Br____操作碼的參數。

Br,BrtrueBrfalse之間的區別在於Br總是分支,另外兩個分別從棧中彈出一個值,並且只有分別是true或false時才分支。 (所以是的,有很大的區別!)_S表示「短格式」,但我不確定這意味着什麼。