2010-12-07 2 views
3

En utilisant Mono.Cecil Je veux réécrire la propriété suivante:Comment injecter un appel à System.Object.Equals avec Mono.Cecil?

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

à ceci:

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

Ceci est juste un extrait de ce que la réécriture sera, mais il est là où j'ai un problème.

L'utilisation de Reflector Je peux voir que le code suivant réécrit la propriété si nécessaire à l'exception de l'appel à System.Object.Equals(). Si vous attendez le code IL être:

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

mais il est écrit:

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

Le code pour écrire l'appel à System.Object.Equals est:

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

La méthode utilisée pour init objectEqualsMethodReference est la suivante:

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; 
} 

Il me semble setMethodWriter.Create() ou GetSystemObjectEqualsMethodReference() est incorrect et aucune quantité de débogage n'a résolu le problème.

La propriété en cours d'écriture et le code pour réécrire la propriété ont la même cible de structure. 3.5 et 4.0 échouent tous les deux. J'utilise la branche principale https://github.com/jbevain/cecil pour créer Mono.Cecil.

complet de code

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

Cela ne résoudra pas le problème que vous décrivez, mais il est néanmoins un problème - vous devez prendre l'une des instructions de ldarg_0. – cdhowie

+0

Je pourrais jurer avoir vu 2 instructions ldarg_0 dans Reflector avant :-). Merci. –

Répondre

6

Cecil, à la différence System.Reflection, fait la distinction entre une référence et une définition, et ceux qui sont scope par module. Cela signifie que vous ne pouvez pas simplement utiliser une méthode de définition d'un autre module dans votre propre. Vous devez créer une référence appropriée à celui-ci. C'est un processus appelé importing dans la terminologie Cecil.

Concrètement, GetSystemObjectEqualsMethodReference retourne une méthode définie dans l'corlib, vous devez créer une référence dans votre module:

Remplacement:

var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly); 

par:

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

Et la fixation de l'IL devrait le faire fonctionner.

De plus, alors que je suis, la méthode:

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; 
} 

serait mieux écrire:

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); 
} 

Et

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

ne fait pas beaucoup de sens si vous ne passez pas new ReaderParameters { ReadSymbols = true } lors de la lecture de l'assemblage.

+0

Parfait. Je vous remercie. J'avais essayé d'importer en cours de route mais évidemment incorrectement. –

1

Vous pouvez jeter un coup d'œil au projet KindOfMagic codeplex.

Il fait presque la même chose, mais un peu mieux - il n'appelle pas Object.Equals(), mais l'opérateur d'égalité est défini sur le type cible.

http://kindofmagic.codeplex.com

Questions connexes