Используйте пространство имен System.Reflection.Emit, чтобы внедрить метод generi c в качестве оболочки другого [C #] - PullRequest
0 голосов
/ 25 марта 2020

Ниже немного контекстной информации ... У меня есть Клиент, который может выполнить метод, предоставляемый сервером, только если этот метод совпадает с этим 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 и просто регистрирует метод (который должен быть выполнен с этим делегатом), и этот метод должен иметь два параметра целых чисел типа (int32 in. net) в качестве входных данных и один параметр целочисленного типа (int 32) в качестве выходного результата.

Пока ничего сложного ... Но я хотел бы сделать некоторые дополнительно и реализовать что-то более читабельное ...

Моя цель - позволить разработчику написать что-то подобное вместо этого:

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

, где [ExternalOperationContractAttribute] - это пользовательский атрибут, созданный ad ho c. То, что я хотел бы сделать, это получить все методы с Reflection, помеченными этим атрибутом, создать 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();
} 

Итак, мои вопросы:

  • Как мне взять элементы из входного массива, привести их к подходящему типу и отправить их в DynamicMethod (я думаю, что эта часть выполняется с помощью инструкции OpCodes.Call)?
  • Как я могу вернуть более одного объекта в массив объектов, используя пространство имен Emit?
  • В любом случае, объяснение предпочтительнее, чем прямое решение :) даже способ директивы программирования может быть оценен. Я ожидаю некоторой конструктивной критики типа «Зачем усложнять то, что легко?» ... Да, я знаю, и мне тоже интересно :) Может быть, чтобы увидеть и узнать что-то новое и попытаться сделать код более читабельным и тонким, что для разработчика совсем не плохо.

Заранее большое спасибо.

1 Ответ

1 голос
/ 26 марта 2020

Насколько я понимаю, до тех пор, пока вы не измените скомпилированную сборку, нет необходимости go опускаться до уровня IL и использовать Emit.

В вашем случае вам просто нужно динамически собрать экземпляр MyMethod, который может вызвать Sum с предоставленными параметрами из массива объектов. Итак, все, что вам нужно, это:

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

Вот когда Express Деревья станут полезными. Предполагая, что вы нашли MethodInfo для Sum с помощью отражения, вы можете построить вышеупомянутую функцию следующим образом:

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