如何使用Mono.Cecil注入System.Object.Equals?

使用Mono.Cecil我想重写以下属性:

public string FirstName { get { return _FirstName; } set { _FirstName = value; } } 

对此:

 public string FirstName { get { return _FirstName; } set { if (System.Object.Equals(_FirstName, value)) { return; } _FirstName = value; } } 

这只是重写的内容,但它是我遇到问题的地方。

使用Reflector我可以看到以下代码根据需要重写属性,除了调用System.Object.Equals()。 如果期望IL代码是:

 call bool [mscorlib]System.Object::Equals(object, object) 

但它被写成:

 call instance void RewriteSharp.Person::.ctor() 

写入对System.Object.Equals的调用的代码是:

 setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference)); 

用于初始化objectEqualsMethodReference的方法是:

 private static MethodReference GetSystemObjectEqualsMethodReference( AssemblyDefinition assembly ) { var typeReference = assembly.MainModule.GetTypeReferences() .Single(t => t.FullName == "System.Object"); var typeDefinition = typeReference.Resolve(); var methodDefinition = typeDefinition.Methods.Single( m => m.Name == "Equals" && m.Parameters.Count == 2 && m.Parameters[0].ParameterType.Name == "Object" && m.Parameters[1].ParameterType.Name == "Object" ); return methodDefinition; } 

在我看来,setMethodWriter.Create()或GetSystemObjectEqualsMethodReference()是不正确的,没有多少调试解决了这个问题。

正在编写的属性和重写属性的代码具有相同的框架目标。 3.5和4.0都失败了。

我正在使用主分支https://github.com/jbevain/cecil来构建Mono.Cecil。

完整的代码清单

 using Mono.Cecil; using Mono.Cecil.Cil; using System; using System.Linq; namespace RewriteNotifyPropertyChanged { class Program { static void Main(string[] args) { var rewrite = "..\\RewriteSharp.dll"; var rewritten = "..\\RewritenSharp.dll"; var typeName = "Person"; var propertyName = "FirstName"; var assembly = AssemblyDefinition.ReadAssembly(rewrite); var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName); var propertyDefintion = typeDefinition.Properties .Single(p => p.Name == propertyName); var setMethodWriter = propertyDefintion.SetMethod.Body.GetILProcessor(); var backingFieldReference = GetBackingFieldReference(typeDefinition, propertyName); var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly); var firstExistingInstruction = setMethodWriter.Body.Instructions[0]; setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Ldarg_0)); setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Ldfld, backingFieldReference)); setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Ldarg_1)); setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference)); setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Brfalse_S, firstExistingInstruction)); setMethodWriter.InsertBefore( firstExistingInstruction, setMethodWriter.Create(OpCodes.Ret)); assembly.Write(rewritten, new WriterParameters { WriteSymbols = true }); Console.WriteLine("Done."); Console.ReadKey(); } private static MethodReference GetSystemObjectEqualsMethodReference( AssemblyDefinition assembly ) { var typeReference = assembly.MainModule.GetTypeReferences() .Single(t => t.FullName == "System.Object"); var typeDefinition = typeReference.Resolve(); var methodDefinition = typeDefinition.Methods.Single( m => m.Name == "Equals" && m.Parameters.Count == 2 && m.Parameters[0].ParameterType.Name == "Object" && m.Parameters[1].ParameterType.Name == "Object" ); return methodDefinition; } private static FieldReference GetBackingFieldReference( TypeDefinition typeDefinition, string propertyName ) { var fieldName = "_" + propertyName; var fieldReference = typeDefinition.Fields.Single(f => f.Name == fieldName); return fieldReference; } } } 

与System.Reflection不同,Cecil对引用和定义进行区分,并且每个模块都有一个范围。 这意味着您不能简单地使用自己内部另一个模块的MethodDefinition。 你必须创建一个适当的引用。 这是一个名为Cecil术语导入的过程。

具体来说, GetSystemObjectEqualsMethodReference返回corlib中定义的方法,您需要在模块中创建对它的引用:

更换:

 var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly); 

通过:

 var objectEqualsMethodReference = assembly.MainModule.Import (GetSystemObjectEqualsMethodReference(assembly)); 

修复IL应该可以使它工作。

此外,虽然我在这,方法:

 private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly) { var typeReference = assembly.MainModule.GetTypeReferences() .Single(t => t.FullName == "System.Object"); var typeDefinition = typeReference.Resolve(); var methodDefinition = typeDefinition.Methods.Single( m => m.Name == "Equals" && m.Parameters.Count == 2 && m.Parameters[0].ParameterType.Name == "Object" && m.Parameters[1].ParameterType.Name == "Object" ); return methodDefinition; } 

会写得更好:

 private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly) { var @object = assembly.MainModule.TypeSystem.Object.Resolve (); return @object.Methods.Single( m => m.Name == "Equals" && m.Parameters.Count == 2 && m.Parameters[0].ParameterType.MetadataType == MetadataType.Object && m.Parameters[1].ParameterType.MetadataType == MetadataType.Object); } 

 assembly.Write(rewritten, new WriterParameters { WriteSymbols = true }); 

如果在阅读程序集时未传递new ReaderParameters { ReadSymbols = true } ,则没有多大意义。

你可以看看KindOfMagic codeplex项目。

它几乎完全相同,但更好一点 – 它不调用Object.Equals(),但在目标类型上定义了相等运算符。

http://kindofmagic.codeplex.com