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