2009-05-17 5 views
2

Étant donné que deux objections ne contiennent pas de boucles de référence, connaissez-vous une méthode qui teste leur égalité de façon «générique» (par réflexion)?Une structure semblable à Equals() pour les classes .NET?

Je veux fondamentalement la même sémantique que l'équivalence de struct, seulement sur les classes.

Répondre

3

Je pense qu'il n'y a pas une telle méthode disponible dans le framework, mais c'est assez facile à écrire. Peut-être pas la plus courte mise en œuvre, mais est semble faire le travail:

private bool AreEqual(object x, object y) 
{ 
    // if both are null, they are equal 
    if (x == null && y == null) 
    { 
     return true; 
    } 
    // if one of them are null, they are not equal 
    else if (x == null || y == null) 
    { 
     return false; 
    } 

    // if they are of different types, they can't be compared 
    if (x.GetType() != y.GetType()) 
    { 
     throw new InvalidOperationException("x and y must be of the same type"); 
    } 

    Type type = x.GetType(); 
    PropertyInfo[] properties = type.GetProperties(); 

    for (int i = 0; i < properties.Length; i++) 
    { 
     // compare only properties that requires no parameters 
     if (properties[i].GetGetMethod().GetParameters().Length == 0) 
     { 
      object xValue = properties[i].GetValue(x, null); 
      object yValue = properties[i].GetValue(y, null); 

      if (properties[i].PropertyType.IsValueType && !xValue.Equals(yValue)) 
      { 
       return false; 
      } 
      else if (!properties[i].PropertyType.IsValueType) 
      { 
       if (!AreEqual(xValue, yValue)) 
       { 
        return false; 
       } 
      } // if 
     } // if 
    } // for 

    return true; 

} 
+0

Cela a plusieurs problèmes, que je suis venu à travers lors de l'utilisation de cela. D'une part, s'il y a un cycle dans le graphe de propriétés - c'est-à-dire, si en naviguant récursivement les propriétés d'un objet vous pouvez vous retrouver avec l'objet original - alors cela conduira à une exception StackOverflowException (j'aurais dû beaucoup donné d'où vient le code). Deuxièmement, si l'un des getter de propriété lance une exception, vous êtes à peu près foutu. Cela ne semble pas trop critique, jusqu'à ce que vous réalisiez que le type System.Type est à la fois cyclique et possède des propriétés qui le lancent. :( – Avish

+0

Alors que le problème cyclique peut facilement être résolu en passant récursivement un ensemble de paires "visitées" (x, y) et en arrêtant la récursion en frappant un cycle, le problème des exceptions ne peut pas être résolu de manière cohérente (attendons-nous même exception de x et y? ou est-ce que nous ignorons juste cette propriété?). En tout cas, ce problème est plus complexe qu'il n'y paraît et je recommande de ne pas faire ce genre de comparaison, sauf si vous le vous le faites, vous pouvez simplement implémenter Equals() sur eux et en avoir fini avec lui.) – Avish

+0

@Avish: très bonnes observations, et dans une certaine mesure la raison pour laquelle je préfère normalement implémenter Equals ou utiliser des méthodes de comparaison personnalisées –

0

Si vous voulez faire sans faire une réflexion sur chaque appel, vous voudrez peut-être envisager de construire une DynamicMethod sur le premier appel et en utilisant au lieu . (J'avais un lien vers l'article qui fait cela, mais je l'ai perdu - désolé -. Googler si vous êtes intéressé essayer)

+0

Ouf! DynamicMethod est Reflection Emit, c'est-à-dire que vous créez un MSIL personnalisé pour chaque propriété, pas forcément difficile, mais pas facile à lire ou à déboguer Si performance est ce problème, en écrivant peut-être un Equals ou IEqualityComparer qui est spécialisé pour le travail ferait mieux la tâche. –

0

BTW

Expression.Lambda<Func<T,T,bool>> Compile() 

peut être utilisé en tant que constructeur de méthode dynamique.

doivent encore utiliser la réflexion tout en construisant la Expresison

0

Voici une version mise à jour de la réponse de Fredrik Mörk qui tient compte Nullable et références récursives:

public static bool AreEqual<T>(T x, T y) => 
    AreEqual(x, y, new HashSet<object>(new IdentityEqualityComparer<object>())); 

private static bool AreEqual(object x, object y, ISet<object> visited) 
{ 
    // if both are null, they are equal 
    if (x == null && y == null) return true; 

    // if one of them are null, they are not equal 
    if (x == null || y == null) return false; 

    // if they are of different types, they can't be compared 
    if (x.GetType() != y.GetType()) 
    { 
     throw new InvalidOperationException("x and y must be of the same type"); 
    } 

    // check for recursive references 
    if (visited.Contains(x)) return true; 
    if (visited.Contains(y)) return true; 
    visited.Add(x); 
    visited.Add(y); 

    var type = x.GetType(); 
    var properties = type.GetProperties(); 

    foreach (var property in properties) 
    { 
     // compare only properties that requires no parameters 
     if (property.GetGetMethod().GetParameters().Length == 0) 
     { 
      object xValue = property.GetValue(x, null); 
      object yValue = property.GetValue(y, null); 

      if (property.PropertyType.IsValueType) 
      { 
       // check for Nullable 
       if (xValue == null && yValue == null) continue; 
       if (xValue == null || yValue == null) return false; 
       if (!xValue.Equals(yValue)) return false; 
      } 

      if (!property.PropertyType.IsValueType) 
      { 
       if (!AreEqual(xValue, yValue, visited)) return false; 
      } 
     } 
    } 

    return true; 
} 

private class IdentityEqualityComparer<T> : IEqualityComparer<T> where T : class 
{ 
    public int GetHashCode(T value) => RuntimeHelpers.GetHashCode(value); 
    public bool Equals(T left, T right) => left == right; 
} 
Questions connexes