使用命名空间 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 };
}
为简化起见,避免了各种检查(null、整数检查等)。
然后,在 class 的初始化程序中,我应该做一些像
这样的事情
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();
}
所以,我的问题:
- 我怎样才能从输入数组中获取元素,将它们转换为合适的类型并将它们发送到 DynamicMethod(我认为这部分是通过
OpCodes.Call
指令完成的)?
- 如何使用 Emit 命名空间 return 将多个对象放入一组对象中?
- 无论如何,解释比直接解决方案更可取 :) 即使是一种编程指令方式也值得赞赏。我期待一些建设性的批评,比如“为什么要把简单的事情复杂化?” ... 是的,我知道,我也想知道 :) 也许看到和学习一些新东西并尝试使代码更具可读性和苗条性,这对开发人员来说根本不是一件坏事。
非常感谢您。
我的理解是只要不修改编译好的程序集,就没有必要深入到IL层面去使用Emit。
在您的情况下,您只需要动态构建 MyMethod
的实例,它可以使用对象数组中提供的参数调用 Sum
。所以你只需要这个:
MyMethod m = (code, p) => new object[1] { Sum((int)p[0], (int)p[1]) };
Register(m);
那时 Express Trees 就派上用场了。假设您使用反射找到 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);
下面是一些上下文信息...
我有一个客户端,只有当该方法与此 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 };
}
为简化起见,避免了各种检查(null、整数检查等)。 然后,在 class 的初始化程序中,我应该做一些像
这样的事情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();
}
所以,我的问题:
- 我怎样才能从输入数组中获取元素,将它们转换为合适的类型并将它们发送到 DynamicMethod(我认为这部分是通过
OpCodes.Call
指令完成的)? - 如何使用 Emit 命名空间 return 将多个对象放入一组对象中?
- 无论如何,解释比直接解决方案更可取 :) 即使是一种编程指令方式也值得赞赏。我期待一些建设性的批评,比如“为什么要把简单的事情复杂化?” ... 是的,我知道,我也想知道 :) 也许看到和学习一些新东西并尝试使代码更具可读性和苗条性,这对开发人员来说根本不是一件坏事。
非常感谢您。
我的理解是只要不修改编译好的程序集,就没有必要深入到IL层面去使用Emit。
在您的情况下,您只需要动态构建 MyMethod
的实例,它可以使用对象数组中提供的参数调用 Sum
。所以你只需要这个:
MyMethod m = (code, p) => new object[1] { Sum((int)p[0], (int)p[1]) };
Register(m);
那时 Express Trees 就派上用场了。假设您使用反射找到 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);