2012-11-28 10 views
6

Ho un codice che confronta 2 PropertyInfos con Equals(). Anche se questo sembra normalmente al lavoro, ho incontrato una strana situazione in cui due riflessa informazioni della struttura oggetti per la stessa proprietà sottostante non sono uguali:Uguaglianza per .NET PropertyInfos

PropertyInfo prop1, prop2; // both are public and not static 
Console.WriteLine(prop1 == prop2); // false ??? 
Console.WriteLine(Equals(prop1, prop2)); // false ??? 
Console.WriteLine(prop1.DeclaringType == prop2.DeclaringType); // true 
Console.WriteLine(prop1.ReturnType == prop2.ReturnType); // true 
Console.WriteLine(prop1.Name == prop2.Name); // true 
Console.WriteLine(prop1.DeclaringType.GetProperties().Contains(prop1)); // true 
Console.WriteLine(prop2.DeclaringType.GetProperties().Contains(prop2)); // false ??? 

Sembra PropertyInfo in realtà non implementare equals(), ma io pensavo che le cache di .NET riflettessero i membri in modo che la stessa istanza venisse sempre restituita. Certamente vedi a.GetType() == b.GetType() tutto il tempo. Non è questo il caso di PropertyInfos?

Alcune altre note: -Questo stranezza è accaduto durante l'esecuzione di un test di NUnit in .NET 4, VS2012, x86 build target -Questo non ha nemmeno accade per tutte le proprietà confrontiamo questo modo, ma non riesce costantemente su un proprietà.

Qualcuno può spiegare questo comportamento?

EDIT: nel caso in cui qualcuno è interessato, qui è la funzione EqualityComparison ho scritto per confrontare MemberInfos:

public class MemberEqualityComparer : EqualityComparer<MemberInfo> { 
    public override bool Equals(MemberInfo @this, MemberInfo that) { 
     if (@this == that) { return true; } 
     if (@this == null || that == null) { return false; } 

         // handles everything except for generics 
        if (@this.MetadataToken != that.MetadataToken 
         || !Equals(@this.Module, that.Module) 
         || this.Equals(@this.DeclaringType, that.DeclaringType)) 
        { 
         return false; 
        } 

        bool areEqual; 
        switch (@this.MemberType) 
        { 
         // constructors and methods can be generic independent of their types, 
         // so they are equal if they're generic arguments are equal 
         case MemberTypes.Constructor: 
         case MemberTypes.Method: 
          var thisMethod = @this as MethodBase; 
          var thatMethod = that as MethodBase; 
               areEqual = thisMethod.GetGenericArguments().SequenceEqual(thatMethod.GetGenericArguments(), 
this); 
          break; 
         // properties, events, and fields cannot be generic independent of their types, 
         // so if we've reached this point without bailing out we just return true. 
         case MemberTypes.Property: 
         case MemberTypes.Event: 
         case MemberTypes.Field: 
          areEqual = true; 
          break; 
         // the system guarantees reference equality for types, so if we've reached this point 
         // without returning true the two are not equal 
         case MemberTypes.TypeInfo: 
         case MemberTypes.NestedType: 
          areEqual = false; 
          break; 
         default: 
          throw new NotImplementedException(@this.MemberType.ToString()); 
    } 

    public override int GetHashCode(MemberInfo memberInfo) { 
     if (memberInfo == null) { return 0; } 

    var hash = @this.MetadataToken 
     ^@this.Module.GetHashCode() 
     ^this.GetHashCode(@this.DeclaringType); 
    return hash; 
    } 
} 
+0

hai visto il codice decompilato? – pylover

+0

@smartcaveman, hai intenzione di restituire false ogni volta che i tipi di dichiarazione sono uguali? Penso che ci dovrebbe essere un "non" lì dentro. Inoltre, le ultime righe del tuo metodo Equals sembrano essere troncate, poiché non ci sono dichiarazioni di ritorno o parentesi di chiusura. – Jax

risposta

5

Sto indovinando che hanno un diverso ReflectedType. Ad esempio, l'ereditarietà:

class A { 
    public int Foo {get;set;} 
} 
class B : A {} 

ora guarda typeof(A).GetProperty("Foo") e typeof(B).GetProperty("Foo").

12

L'identità dell'oggetto è solo promessa per la classe Tipo, non per le altre classi di riflessione. A possibilmente il modo corretto di confrontare per l'uguaglianza consiste nel verificare che le proprietà abbiano lo stesso token di metadati e provengano dallo stesso modulo. Quindi prova questo:

bool equal = prop1.MetadataToken == prop2.MetadataToken && 
      prop1.Module.Equals(prop2.Module); 

Che ha senso finché si applica l'ecma 335. Non ho potuto testarlo contro il tuo codice poiché non lo hai pubblicato. Quindi provalo.

+0

Sembra funzionare, ad eccezione delle proprietà dei tipi generici (a meno che non si desideri List .Count == List .Count). – ChaseMedallion

+1

@ChaseMedallion, Che ne dici di aggiungere una congiunzione con 'prop1.DeclaringType == prop2.DeclaringType' come bene? Sembra che ciò riguarderebbe il caso – smartcaveman

+0

@smartcaveman: DeclaringType gestisce bene i generici delle proprietà, poiché le proprietà stesse non possono essere generiche (solo il tipo dichiarante). Tuttavia, per estendere questo metodo a tutti i membri è necessario eseguire alcuni controlli aggiuntivi per gestire metodi generici. Vedere il mio codice pubblicato (nella domanda) per un set (apparentemente) completo di assegni per membri arbitrari. Noterai il controllo DeclaringType verso l'alto. – ChaseMedallion