Как ввести вызов System.Object.Equals с помощью Mono.Cecil?
Используя 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;
}
}
}
2 ответа
Сесил, в отличие от System.Reflection, делает различие между ссылкой и определением, и они определяются для каждого модуля. Это означает, что вы не можете просто использовать MethodDefinition из другого модуля внутри вашего. Вы должны создать правильную ссылку на него. Этот процесс называется импортом в терминологии Сесила.
В частности, 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.
Он делает почти то же самое, но немного лучше - он не вызывает Object.Equals(), но оператор равенства определен для целевого типа.