我将使用反射来在运行时创建一个方法的副本。
以下是我的代码。
最后一行抛出了一个带有以下消息的异常:
以下方法也无法解决问题。
以下是我的代码。
public static R CopyMethod<T, R>(Func<T, R> f, T t)
{
AppDomain currentDom = Thread.GetDomain();
AssemblyName asm = new AssemblyName();
asm.Name = "DynamicAssembly";
AssemblyBuilder abl = currentDom.DefineDynamicAssembly(asm, AssemblyBuilderAccess.Run);
ModuleBuilder mbl = abl.DefineDynamicModule("Module");
TypeBuilder tbl = mbl.DefineType("Type");
var info = f.GetMethodInfo();
MethodBuilder mtbl = tbl.DefineMethod(info.Name, info.Attributes, info.CallingConvention, info.ReturnType, info.GetParameters().Select(x => x.ParameterType).ToArray());
byte[] il = f.Method.GetMethodBody().GetILAsByteArray();
mtbl.CreateMethodBody(il, il.Length);
Type type = tbl.CreateType();
Func<T, R> method = type.GetMethod(info.Name).CreateDelegate(typeof(Func<T, R>)) as Func<T, R>;
return method(t);
}
最后一行抛出了一个带有以下消息的异常:
还有其他方法吗?我更希望能够获取方法的解析树,而不是直接使用IL。 编辑1: 我正在测试以下函数。Common Language Runtime 检测到一个无效程序。
public static int Fib(int n)
{
/*if (n < 2)
return 1;
return Fib(n - 1) + Fib(n - 2);*/
return n;
}
以下是测试行。
int x = Copy.CopyMethod(Copy.Fib, 10);
编辑2::
Rob的答案有助于解决上述问题。然而,当使用稍微复杂一些的Fib()
方法(例如被注释掉的Fibonacci方法)时,程序会崩溃并显示以下消息。
找不到索引。(HRESULT异常: 0x80131124)
编辑3::
我尝试了评论中提出的几个建议,但在动态程序集中无法找到元数据标记。
public static R CopyMethod<T, R>(Func<T, R> f, T t)
{
AppDomain currentDom = Thread.GetDomain();
AssemblyName asm = new AssemblyName("DynamicAssembly");
AssemblyBuilder abl = currentDom.DefineDynamicAssembly(asm, AssemblyBuilderAccess.Run);
ModuleBuilder mbl = abl.DefineDynamicModule("Module");
TypeBuilder tbl = mbl.DefineType("Type");
MethodInfo info = f.GetMethodInfo();
MethodBuilder mtbl = tbl.DefineMethod(info.Name, info.Attributes, info.CallingConvention, info.ReturnType, info.GetParameters().Select(x => x.ParameterType).ToArray());
MethodBody mb = f.Method.GetMethodBody();
byte[] il = mb.GetILAsByteArray();
OpCode[] opCodes = GetOpCodes(il);
Globals.LoadOpCodes();
MethodBodyReader mbr = new MethodBodyReader(info);
string code = mbr.GetBodyCode();
Console.WriteLine(code);
ILGenerator ilg = mtbl.GetILGenerator();
ilg.DeclareLocal(typeof(int[]));
ilg.DeclareLocal(typeof(int));
for (int i = 0; i < opCodes.Length; ++i)
{
if (opCodes[i].OperandType == OperandType.InlineType)
{
int token;
Type tp = info.Module.ResolveType(token = BitConverter.ToInt32(il, i + 1), info.DeclaringType.GetGenericArguments(), info.GetGenericArguments());
ilg.Emit(opCodes[i], tp.MetadataToken);
i += 4;
continue;
}
if (opCodes[i].FlowControl == FlowControl.Call)
{
int token;
MethodBase mi = info.Module.ResolveMethod(token = BitConverter.ToInt32(il, i + 1));
ilg.Emit(opCodes[i], mi.MetadataToken);
i += 4;
continue;
}
ilg.Emit(opCodes[i]);
}
Type type = tbl.CreateType();
Func<T, R> method = type.GetMethod(info.Name).CreateDelegate(typeof(Func<T, R>)) as Func<T, R>;
return method(t);
}
以下方法也无法解决问题。
var sigHelp = SignatureHelper.GetLocalVarSigHelper(mtbl.Module);
mtbl.SetMethodBody(il, mb.MaxStackSize, sigHelp.GetSignature(), null, new int[] { 3 });
我可以通过以下方式更改元数据标记来修复递归函数调用(我意识到这种方法并不适用于所有情况,但我正在尝试以某种方式使其正常工作)。
if (opCodes[i].FlowControl == FlowControl.Call)
{
ilg.Emit(opCodes[i], mtbl);
i += 4;
}
我可以使用在相关问题的回答中建议的方法构建一个动态方法:Reference a collection from IL constructed method。然而,在尝试在这里进行相同操作时,它失败了。
AssemblyBuilderAccess.RunAndSave
,这样您就可以将创建的程序集保存到磁盘上。接下来,您可以使用 Peverify.exe 来验证一切是否正常。您的Fib
函数无法运行,因为它在调用指令中包含元数据标记。 - thehennyyIL_0010: /* 28 | (06)000002 */ call int32 ConsoleApplication1.Program::Fib(int32)
。你需要找到06000002,询问程序集那是什么,然后在新方法的上下文中用自己的元数据标记值替换它。 - elchidoILGenerator.Emit()
永远不会将元数据标记作为参数,因为这些标记必须由动态程序集本身生成,元数据标记仅在其模块的范围内有效。相反,您将始终传递xxxInfo
或xxxBuilder
实例。为了使事情更清晰一些,在大多数情况下,xxxBuilder类是其对应的xxxInfo类的子类,因此可以使用它。当您发出的代码变得更加复杂并涉及其他动态生成的成员时,请记住这一点。 - thehennyyMethodBuilder
对象mtbl
发出调用。否则,获取该方法的MethodInfo
并使用它发出调用。 - elchido