简体   繁体   English

使用命名空间 System.Reflection.Emit 注入一个泛型方法作为另一个方法的包装器 [C#]

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

Below a bit of contextual info... I've a Client that can execute a method exposed by the server only if that method matches with this delegate下面是一些上下文信息......我有一个客户端,只有当该方法与此delegate匹配时,它才能执行服务器公开的方法

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

so... for example if I want to expose a new method that executes the sum of two integers, I should do something like this :所以......例如,如果我想公开一个执行两个整数总和的新方法,我应该做这样的事情:

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

Every kind of checks (null, integer checks and so on) are avoid for semplicity.为了简单起见,避免进行各种检查(空值、整数检查等)。 Then, in the initializer of the class I should do somethik like然后,在类的初始化程序中,我应该做类似的事情

Register(Sum);

Where Register accept the delegates MyMethod and does simply registering a method (that shall be executed with that delegate) and that method shall have two parameters of type integers (int32 in .net) as input and one parameter of type integer (int 32) as output result.其中Register接受委托 MyMethod 并只注册一个方法(应与该委托一起执行),该方法应具有两个整数类型的参数(.net 中的 int32)作为输入和一个整数类型的参数(int 32)作为输出结果。

So far nothing hard... But I'd like to do some extra and realize something more readable...到目前为止,没什么难的......但我想做一些额外的事情并实现一些更具可读性的东西......

My goal is to let a developer write something like that instead:我的目标是让开发人员编写类似的东西:

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

where [ExternalOperationContractAttribute] is a custom attribute created ad hoc.其中[ExternalOperationContractAttribute]是临时创建的自定义属性。 What I would like to do is get all method with Reflection marked with that attribute, create a DynamicMethod with the same input and ouput parameter of the delegates and inject the instructions so that the new method behaves exactly as the one withou the ExternalOperationCOntractAttribute.我想要做的是获取所有带有该属性标记的反射的方法,使用委托的相同输入和输出参数创建一个DynamicMethod并注入指令,以便新方法的行为与没有 ExternalOperationCOntractAttribute 的方法完全相同。

I've discovered the System.Reflection.Emit namespace and I think that it's somwthing useful to that scope.我发现了 System.Reflection.Emit 命名空间,我认为它对该范围很有用。 So I tried to implement something but with poor results and frankly speaking a frankenstein code got taking some pieces of code here and there on the network.所以我试图实现一些东西,但结果很差,坦率地说,一个弗兰肯斯坦代码在网络上到处都是一些代码。 Below is a part:下面是一部分:

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

So, my questions:所以,我的问题:

  • How can I have take elements from the input array, cast them to the opportune type and send them to the DynamicMethod (I think this part is done via the OpCodes.Call instruction)?我怎样才能从输入数组中获取元素,将它们转换为OpCodes.Call类型并将它们发送到 DynamicMethod(我认为这部分是通过OpCodes.Call指令完成的)?
  • How can I return more than one object into an array of objects using the Emit namespace?如何使用 Emit 命名空间将多个对象返回到一组对象中?
  • Anyway, an explanation is preferred than a direct solution :) even a way of programming directive could be appreciated.无论如何,解释比直接解决方案更受欢迎:) 甚至可以欣赏一种编程指令的方式。 I expect some constructive criticism like 'Why complicate what it's easy?'我期待一些建设性的批评,比如“为什么要把容易的事情复杂化?” ... Yes, I know and I'm wondering it, too :) Maybe to see and learn something new and try to make the code more readable and slim that for a developer is not a bad thing at all. ... 是的,我知道,我也很想知道 :) 或许可以看到和学习​​一些新的东西,并尝试使代码更具可读性和精简性,这对开发人员来说根本不是一件坏事。

Thank you so much, in advance.非常感谢,提前。

My understanding is as long as you don't modify the compiled assembly, there's no need to go down to the IL level and use Emit.我的理解是只要不修改编译后的程序集,就没有必要下到IL级别使用Emit。

In your case, you just need to dynamically build an instance of MyMethod , which can call Sum with provided parameters from an object array.在您的情况下,您只需要动态构建MyMethod的实例,它可以使用对象数组中提供的参数调用Sum So all you need is this:所以你只需要这个:

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

That's when Express Trees become useful.这就是Express Trees变得有用的时候。 Assuming you have found MethodInfo for Sum using reflection, you could build the above function like this:假设您已经使用反射找到了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;
}

You could then register Sum like this:然后,您可以像这样注册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