[英]How to inject call to System.Object.Equals with Mono.Cecil?
Using Mono.Cecil I want to rewrite the following property: 使用Mono.Cecil我想重写以下属性:
public string FirstName
{
get { return _FirstName; }
set
{
_FirstName = value;
}
}
to this: 对此:
public string FirstName
{
get { return _FirstName; }
set
{
if (System.Object.Equals(_FirstName, value))
{
return;
}
_FirstName = value;
}
}
This is just a snippet of what the rewrite will be but it is where I'm having a problem. 这只是重写的内容,但它是我遇到问题的地方。
Using Reflector I can see the following code rewrites the property as required except the call to System.Object.Equals(). 使用Reflector我可以看到以下代码根据需要重写属性,除了调用System.Object.Equals()。 If expect the IL code to be: 如果期望IL代码是:
call bool [mscorlib]System.Object::Equals(object, object)
but it is being written as: 但它被写成:
call instance void RewriteSharp.Person::.ctor()
The code to write the call to System.Object.Equals is: 写入对System.Object.Equals的调用的代码是:
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));
The method used to init objectEqualsMethodReference is: 用于初始化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;
}
It seems to me setMethodWriter.Create() or GetSystemObjectEqualsMethodReference() is incorrect and no amount of debugging has solved the problem. 在我看来,setMethodWriter.Create()或GetSystemObjectEqualsMethodReference()是不正确的,没有多少调试解决了这个问题。
The property being written and code to rewrite the property have the same framework target. 正在编写的属性和重写属性的代码具有相同的框架目标。 3.5 and 4.0 both fail. 3.5和4.0都失败了。
I'm using the master branch https://github.com/jbevain/cecil to build Mono.Cecil. 我正在使用主分支https://github.com/jbevain/cecil来构建Mono.Cecil。
Complete Code Listing 完整的代码清单
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;
}
}
}
Cecil, unlike System.Reflection, makes the distinction between a reference and a definition, and those are scoped per module. Cecil与System.Reflection不同,它区分了引用和定义,并且每个模块都有一个范围。 It means that you can't simply use a MethodDefinition from another module inside your own. 这意味着您不能简单地使用自己内部另一个模块的MethodDefinition。 You have to create a proper reference to it. 你必须创建一个适当的引用。 This is a process called importing in the Cecil terminology. 这是一个名为Cecil术语导入的过程。
Concretely, GetSystemObjectEqualsMethodReference
returns a method defined in the corlib, you need to create a reference to it in your module : 具体来说, GetSystemObjectEqualsMethodReference
返回corlib中定义的方法,您需要在模块中创建对它的引用:
Replacing: 更换:
var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);
by: 通过:
var objectEqualsMethodReference = assembly.MainModule.Import (GetSystemObjectEqualsMethodReference(assembly));
And fixing the IL should make it work. 修复IL应该可以使它工作。
Also, while I'm at it, the method: 此外,虽然我在,它的方法:
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;
}
Would be better written as: 会写得更好:
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);
}
And 和
assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });
Doesn't make much sense if you don't pass new ReaderParameters { ReadSymbols = true }
when reading the assembly. 如果在阅读程序集时未传递new ReaderParameters { ReadSymbols = true }
,则没有多大意义。
You could take a look at KindOfMagic codeplex project. 你可以看看KindOfMagic codeplex项目。
It does almost the same, but little bit better - it does not call Object.Equals(), but equality operator defined on the target type. 它几乎一样,但更好一点 - 它不调用Object.Equals(),但在目标类型上定义了相等运算符。
http://kindofmagic.codeplex.com http://kindofmagic.codeplex.com
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.