简体   繁体   中英

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

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.

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. 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.

I've discovered the System.Reflection.Emit namespace and I think that it's somwthing useful to that scope. 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)?
  • How can I return more than one object into an array of objects using the Emit namespace?
  • 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.

In your case, you just need to dynamically build an instance of MyMethod , which can call Sum with provided parameters from an object array. 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. Assuming you have found MethodInfo for Sum using reflection, you could build the above function like this:

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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM