Using Mono.Cecil I want to rewrite the following property:
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(). If expect the IL code to be:
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:
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));
The method used to init objectEqualsMethodReference is:
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.
The property being written and code to rewrite the property have the same framework target. 3.5 and 4.0 both fail.
I'm using the master branch https://github.com/jbevain/cecil to build 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;
}
}
}
You could take a look at KindOfMagic codeplex project.
It does almost the same, but little bit better - it does not call Object.Equals(), but equality operator defined on the target type.
http://kindofmagic.codeplex.com
Cecil, unlike System.Reflection, makes the distinction between a reference and a definition, and those are scoped per module. It means that you can't simply use a MethodDefinition from another module inside your own. You have to create a proper reference to it. This is a process called importing in the Cecil terminology.
Concretely,
GetSystemObjectEqualsMethodReference
returns a method defined in the corlib, you need to create a reference to it in your module :Replacing:
by:
And fixing the IL should make it work.
Also, while I'm at it, the method:
Would be better written as:
And
Doesn't make much sense if you don't pass
new ReaderParameters { ReadSymbols = true }
when reading the assembly.