Un profondo Equals struct-like () per le classi .NET?
-
22-08-2019 - |
Domanda
Dati due obiettare che non contengono i cicli di riferimento al loro interno, non si conosce un metodo che mette alla prova la loro uguaglianza in modo "generico" (attraverso la riflessione)?
Io fondamentalmente voglio la stessa semantica equivalenza struct, solo su classi.
Soluzione
Credo che non v'è tale metodo disponibile nel quadro, ma è abbastanza facilmente scritto. Forse non è il più breve attuazione, ma è sembra fare il lavoro:
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;
}
Altri suggerimenti
Se si vuole fare questo senza facendo riflessione su ogni chiamata, si potrebbe prendere in considerazione la costruzione di un DynamicMethod
sulla prima invocazione e l'utilizzo che, invece. (Avevo un link per l'articolo che fa questo, ma l'ho perso - mi dispiace -. Provare Googling se interessati)
tra
Expression.Lambda<Func<T,T,bool>> Compile()
può essere utilizzato come un costruttore metodo dinamico.
devono ancora utilizzare la riflessione, mentre la costruzione del Expresison
Ecco una versione aggiornata dalla risposta di Fredrik Mörk che tenga conto Nullable
e riferimenti ricorsivi:
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;
}