簡體   English   中英

使用命名空間 System.Reflection.Emit 注入一個泛型方法作為另一個方法的包裝器 [C#]

[英]Use namespace System.Reflection.Emit to inject a generic method as a wrapper of another one [C#]

下面是一些上下文信息......我有一個客戶端,只有當該方法與此delegate匹配時,它才能執行服務器公開的方法

public delegate object[] MyMethod (int code, object[] parameters);

所以......例如,如果我想公開一個執行兩個整數總和的新方法,我應該做這樣的事情:

private static object[] Sum(int code, object[] parameters)
{
    int firstNumber = int.Parse(parameters[0].ToString()),
        secondNumber = int.Parse(parameters[1].ToString());

    return new object[1] { firstNumber + secondNumber };
}

為了簡單起見,避免進行各種檢查(空值、整數檢查等)。 然后,在類的初始化程序中,我應該做類似的事情

Register(Sum);

其中Register接受委托 MyMethod 並只注冊一個方法(應與該委托一起執行),該方法應具有兩個整數類型的參數(.net 中的 int32)作為輸入和一個整數類型的參數(int 32)作為輸出結果。

到目前為止,沒什么難的......但我想做一些額外的事情並實現一些更具可讀性的東西......

我的目標是讓開發人員編寫類似的東西:

[ExternalOperationContract]
public static int Sum(int a, int b) => a + b;

其中[ExternalOperationContractAttribute]是臨時創建的自定義屬性。 我想要做的是獲取所有帶有該屬性標記的反射的方法,使用委托的相同輸入和輸出參數創建一個DynamicMethod並注入指令,以便新方法的行為與沒有 ExternalOperationCOntractAttribute 的方法完全相同。

我發現了 System.Reflection.Emit 命名空間,我認為它對該范圍很有用。 所以我試圖實現一些東西,但結果很差,坦率地說,一個弗蘭肯斯坦代碼在網絡上到處都是一些代碼。 下面是一部分:

public static void Main()
{
    Type[] sumArgs = { typeof(int), typeof(object[]) };

    // Create a dynamic method with the name "Sum", a return type
    // of object[], and two parameters whose types are specified by the
    // array helloArgs. Create the method in the module that
    // defines the Test class.
    DynamicMethod hello = new DynamicMethod("Sum",
        typeof(object[]),
        sumArgs,
        typeof(Test).Module);

    // Get the overload of Console.WriteLine that has one
    // String parameter.
    MethodInfo myMethod =
        typeof(Test).GetMethod("Sum");

    ILGenerator ilgen = hello.GetILGenerator();
    var il = ilgen;    
    il.Emit(OpCodes.Ldarg_0);

    LocalBuilder arr = il.DeclareLocal(typeof(string));
    il.Emit(OpCodes.Ldc_I4_1);
    il.Emit(OpCodes.Newarr, typeof(string));
    il.Emit(OpCodes.Stloc, arr);
    il.Emit(OpCodes.Ldloc, arr);
    il.Emit(OpCodes.Ldc_I4_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Stelem_I4);

    il.Emit(OpCodes.Ldloc, arr);
    il.Emit(OpCodes.Ldc_I4_0);
    il.Emit(OpCodes.Ldelem_I4);

    il.Emit(OpCodes.Call, myMethod);

    il.Emit(OpCodes.Ret);

    // Create a delegate that represents the dynamic method. This
    // action completes the method, and any further attempts to
    // change the method will cause an exception.
    MethodCallBack hi =
        (MethodCallBack)hello.CreateDelegate(typeof(MethodCallBack));

    Register(hi);

    Console.Read();
} 

所以,我的問題:

  • 我怎樣才能從輸入數組中獲取元素,將它們轉換為OpCodes.Call類型並將它們發送到 DynamicMethod(我認為這部分是通過OpCodes.Call指令完成的)?
  • 如何使用 Emit 命名空間將多個對象返回到一組對象中?
  • 無論如何,解釋比直接解決方案更受歡迎:) 甚至可以欣賞一種編程指令的方式。 我期待一些建設性的批評,比如“為什么要把容易的事情復雜化?” ... 是的,我知道,我也很想知道 :) 或許可以看到和學習​​一些新的東西,並嘗試使代碼更具可讀性和精簡性,這對開發人員來說根本不是一件壞事。

非常感謝,提前。

我的理解是只要不修改編譯后的程序集,就沒有必要下到IL級別使用Emit。

在您的情況下,您只需要動態構建MyMethod的實例,它可以使用對象數組中提供的參數調用Sum 所以你只需要這個:

MyMethod m = (code, p) => new object[1] { Sum((int)p[0], (int)p[1]) };
Register(m);

這就是Express Trees變得有用的時候。 假設您已經使用反射找到了Sum MethodInfo ,您可以像這樣構建上述函數:

private static MyMethod BuildMyMethod(MethodInfo mi)
{
    var code = Expression.Parameter(typeof(int), "code");
    var objParameters = Expression.Parameter(typeof(object[]), "p");

    // (pi.ParameterType)p[0]
    // e.g. (int)p[0]
    var parameters = mi.GetParameters().Select(
        (pi, index) => Expression.Convert(Expression.ArrayIndex(
            objParameters, Expression.Constant(index)
        ), pi.ParameterType));

    // e.g Sum(((int)p[0]), (int)p[1])
    var callMethod = Expression.Call(mi, parameters);

    // new object[] { Sum(((int)p[0]), (int)p[1]) }
    var returnResult = Expression.NewArrayInit(
        typeof(object),
        Expression.Convert(callMethod, typeof(object)));

    var myMethod = Expression.Lambda<MyMethod>(returnResult, code, objParameters).Compile();
    return myMethod;
}

然后,您可以像這樣注冊Sum

var myMethod = BuildMyMethod(sumMethod);

// If you call the built delegate:
// result = new object[1] { 3 }
var result = myMethod(1, new object[] { 1, 2 });

Register(myMethod);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM