2013-05-13 11 views
9

Dati due oggetti identici tipo anonimo:È sicuro utilizzare GetHashCode per confrontare identici tipi di Anonymous?

{msg:"hello"} //anonType1 
{msg:"hello"} //anonType2 

e supponiamo che non hanno risolto allo stesso tipo (ad esempio, potrebbero essere definiti in diversi assiemi)

anonType1.Equals(anonType2); //false 

Inoltre, assumere che compilare il tempo, non riesco a ottenere la struttura di uno (per esempio, anonType1) perché l'API espone solo object

Quindi, per confrontarli, ho pensato alle seguenti tecniche:

  1. Utilizzare la riflessione per ottenere la proprietà msg su anonType1 per il confronto.
  2. Cast anonType1 a un tipo dynamic e riferimento .msg sull'elemento dinamico per il confronto
  3. confrontare il risultato di .GetHashCode() su ciascun oggetto.

La mia domanda è: è sicuro usare l'opzione 3? Cioè è ragionevole assumere che l'implementazione .GetHashcode() restituirà sempre lo stesso valore per i tipi anonimi strutturati in modo indentico, ma diversi nelle versioni attuali e future del framework .NET?

+0

Nota: ho aggiunto un operatore di confronto 'Expression' a base di membro a membro - potrebbe essere utile –

+0

brillante, grazie. –

risposta

5

Interessante domanda. Le specifiche definiscono che i metodi Equals e GetHashcode (si noti che l'errore di battitura nella specifica!) Si comporteranno per istanze dello stesso tipo, tuttavia l'implementazione non è definita. Come succede, l'attuale compilatore MS C# implementa questo usando numeri magici come un seme di -1134271262 e un moltiplicatore di -1521134295. Ma questo non fa parte della specifica. Teoricamente questo potrebbe cambiare radicalmente tra le versioni del compilatore C# e risponderebbe comunque a ciò che è necessario. Quindi se i 2 assembly non sono compilati dallo stesso compilatore, non c'è alcuna garanzia. In effetti, sarebbe "valido" (ma improbabile) che il compilatore pensasse a un nuovo valore seme ogni volta che compila.

Personalmente, guarderei le tecniche IL o Expression per farlo. Confrontando oggetti dalla forma simile a livello di membro per nome è abbastanza facile fare con Expression.

Per info, ho anche guardato come mcs (il compilatore Mono) implementa GetHashCode, e è diverso; invece di seme e moltiplicatore, utilizza una combinazione di seme, xor, moltiplicatore, turni e aggiunte. Quindi lo stesso tipo compilato da Microsoft e Mono avrà molto diverso GetHashCode.

static class Program { 
    static void Main() { 
     var obj = new { A = "abc", B = 123 }; 
     System.Console.WriteLine(obj.GetHashCode()); 
    } 
} 
  • Mono: -2077468848
  • Microsoft: -617335881

Fondamentalmente, non credo che si può garantire questo.


ne dite:

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
class Foo 
{ 
    public string A { get; set; } 
    public int B; // note a field! 
    static void Main() 
    { 
     var obj1 = new { A = "abc", B = 123 }; 
     var obj2 = new Foo { A = "abc", B = 123 }; 
     Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // True 

     obj1 = new { A = "abc", B = 123 }; 
     obj2 = new Foo { A = "abc", B = 456 }; 
     Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False 

     obj1 = new { A = "def", B = 123 }; 
     obj2 = new Foo { A = "abc", B = 456 }; 
     Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False 
    } 

} 

public static class MemberwiseComparer 
{ 
    public static bool AreEquivalent(object x, object y) 
    { 
     // deal with nulls... 
     if (x == null) return y == null; 
     if (y == null) return false; 
     return AreEquivalentImpl((dynamic)x, (dynamic)y); 
    } 
    private static bool AreEquivalentImpl<TX, TY>(TX x, TY y) 
    { 
     return AreEquivalentCache<TX, TY>.Eval(x, y); 
    } 
    static class AreEquivalentCache<TX, TY> 
    { 
     static AreEquivalentCache() 
     { 
      const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; 
      var xMembers = typeof(TX).GetProperties(flags).Select(p => p.Name) 
       .Concat(typeof(TX).GetFields(flags).Select(f => f.Name)); 
      var yMembers = typeof(TY).GetProperties(flags).Select(p => p.Name) 
       .Concat(typeof(TY).GetFields(flags).Select(f => f.Name)); 
      var members = xMembers.Intersect(yMembers); 

      Expression body = null; 
      ParameterExpression x = Expression.Parameter(typeof(TX), "x"), 
           y = Expression.Parameter(typeof(TY), "y"); 
      foreach (var member in members) 
      { 
       var thisTest = Expression.Equal(
        Expression.PropertyOrField(x, member), 
        Expression.PropertyOrField(y, member)); 
       body = body == null ? thisTest 
        : Expression.AndAlso(body, thisTest); 
      } 
      if (body == null) body = Expression.Constant(true); 
      func = Expression.Lambda<Func<TX, TY, bool>>(body, x, y).Compile(); 
     } 
     private static readonly Func<TX, TY, bool> func; 
     public static bool Eval(TX x, TY y) 
     { 
      return func(x, y); 
     } 
    } 
} 
+0

Se esistesse una specifica che definisse con precisione le classi anonime che i compilatori devono creare, l'interoperabilità sarebbe banale. Senza una specifica per come dovrebbero apparire le classi generate dal compilatore, non vedo come tali classi possano essere interoperabili anche se volessero esserlo. Il fatto che alcune classi arbitrarie abbiano proprietà i cui nomi e valori corrispondono a quelli di una classe anonima non significa che un'istanza della prima debba essere paragonata a un'istanza di quest'ultima. – supercat

+0

@supercat d'altra parte, è improbabile che si confrontino 2 oggetti se la loro non è una * semantica * pseudo-equivalenza tra di loro –

+0

Per tutti i non nulli 'X' e' Y' di tipi con sostituzioni non interrotte di 'Equals (oggetto)', '((oggetto) X). Equals (Y)' e '((oggetto) Y). Equals (X)' dovrebbe sempre restituire lo stesso valore, senza eccezioni. Avere un tipo riporta le sue istanze come uguali a cose di un tipo non correlato che non ne sa nulla è tale da causare il malfunzionamento di raccolte come 'Dictionary ' se gli oggetti di entrambi i tipi sono memorizzati in esso. – supercat

Problemi correlati