Как внедрить вызов System.Object.Equals с помощью Mono.Cecil? - PullRequest
3 голосов
/ 07 декабря 2010

Используя 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 ]

6 голосов
/ 07 декабря 2010

Сесил, в отличие от 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 } при чтении сборки.

1 голос
/ 05 января 2011

Вы можете взглянуть на проект кодекса KindOfMagic.

Он делает почти то же самое, но немного лучше - он не вызывает Object.Equals (), но оператор равенства определен для целевого типа.

http://kindofmagic.codeplex.com

...