C# 替换方法';使用Mono.Cecil的另一种方法的Body?

C# 替换方法';使用Mono.Cecil的另一种方法的Body?,c#,reflection,cil,reflection.emit,mono.cecil,C#,Reflection,Cil,Reflection.emit,Mono.cecil,使用Mono.Cecil将目标MethodDefinition的Body设置为源MethodDefinition的Body看起来非常简单。对于简单的方法,这是可行的。但是对于某些使用自定义类型的方法(例如初始化新对象),它将不起作用(在写回程序集时抛出异常) 这是我的密码: //in current app public class Form1 { public string Test(){ return "Modified Test"; } } //in another asse

使用
Mono.Cecil
将目标
MethodDefinition
Body
设置为源
MethodDefinition
Body
看起来非常简单。对于简单的方法,这是可行的。但是对于某些使用自定义类型的方法(例如初始化新对象),它将不起作用(在写回程序集时抛出异常)

这是我的密码:

//in current app
public class Form1 {
  public string Test(){
   return "Modified Test";
  }
}
//in another assembly
public class Target {
  public string Test(){
    return "Test";
  }
}

//the copying code, this works for the above pair of methods
//the context here is of course in the current app
var targetAsm = AssemblyDefinition.ReadAssembly("target_path");
var mr1 = targetAsm.MainModule.Import(typeof(Form1).GetMethod("Test"));
var targetType = targetAsm.MainModule.Types.FirstOrDefault(e => e.Name == "Target");
var m2 = targetType.Methods.FirstOrDefault(e => e.Name == "Test");
var m1 = mr1.Resolve();
var m1IL = m1.Body.GetILProcessor();

foreach(var i in m1.Body.Instructions.ToList()){
   var ci = i;
   if(i.Operand is MethodReference){
      var mref = i.Operand as MethodReference;
      ci = m1IL.Create(i.OpCode, targetType.Module.Import(mref));
   }
   else if(i.Operand is TypeReference){
      var tref = i.Operand as TypeReference;
      ci = m1IL.Create(i.OpCode, targetType.Module.Import(tref));
   }
   if(ci != i){
       m1IL.Replace(i, ci);
   }
}
//here the source Body should have its Instructions set imported fine
//so we just need to set its Body to the target's Body
m2.Body = m1.Body;
//finally write to another output assembly
targetAsm.Write("modified_target_path");
上面的代码没有在任何地方引用过,我只是自己尝试了一下,发现它适用于简单的情况(比如上面发布的2种方法
Test
)。但如果源方法(在当前应用程序中定义)包含某些类型引用(例如某些构造函数init…),则如下所示:

public class Form1 {
  public string Test(){
   var u = new Uri("SomeUri");
   return u.AbsolutePath;
  }
}
然后在回写程序集时它将失败。引发的异常为
ArgumentException
,并显示以下消息:

“成员'System.Uri'在另一个模块中声明,需要导入”

事实上,我以前遇到过类似的消息,但它是用于类似于(
string.Concat
)之类的方法调用的。这就是为什么我尝试导入
MethodReference
(您可以在我发布的代码中的
foreach
循环中看到
if
)。这真的对那个案子起了作用


但这种情况不同,我不知道如何正确导入已使用/引用的类型(在本例中是
System.Uri
)。据我所知,应该使用导入的
结果,对于
MethodReference
,您可以看到该结果用于替换每个
指令的
操作数。但是对于这种情况下的类型引用,我完全不知道如何进行。

我在问题中发布的所有代码都很好,但还不够。实际上,异常消息:

“成员'System.Uri'在另一个模块中声明,需要导入”

投诉
变量定义
变量类型
。我只导入指令,而不导入变量(它们只是从源
MethodBody
中精确引用)。因此,解决方案是我们也需要以相同的方式导入变量(可能还需要导入
ExceptionHandler
,因为
ExceptionHandler
具有应导入的
CatchType
)。 下面是要导入的类似代码
VariableDefinition

var vars = m1.Body.Variables.ToList();
m1.Body.Variables.Clear();
foreach(var v in vars){
   var nv = new VariableDefinition(v.Name, targetType.Module.Import(v.VariableType));
   m1.Body.Variables.Add(nv);
}

用对新方法的调用替换主体不是更简单吗?@Jeroenmoster这里的源
Test
方法只是一个简单的方法,实际上它可以是任何复杂的代码(包含几十行…)。因此,如果我们每次都手动将这些代码转换为
指令
,这将是一件困难且毫无意义的事情。我想使用一个现有的方法来替换在另一个程序集中定义的另一个方法的代码。我真的认为这在Mono.Cecil.No中是可行的,我的观点是——您想要的方法体已经正确编译(包括类型和程序集引用以及整个过程)。与其尝试将其移植到一个新的主体中,为什么不将
Source.Test
方法主体替换为调用
Target.Test
?(如果存在一个单独的程序集是一个问题,请先合并它们。)无论源或目标有多复杂,这都会起作用。@Jeroenmoster我想我不明白你的意思,我要替换的是
目标。Test
,而只是它的
主体。因此,当
Target
类(通过另存为新程序集进行修改后)在其他地方(而不是在我当前的应用程序中)使用时,
Test
方法将执行我想要的操作(通过在当前应用程序中定义一个伪方法
Test
,以及源方法)。我认为您必须使用和.Net Reflector来执行此操作。