Come rapidamente di verificare se due oggetti di trasferimento dati sono uguali proprietà in C#?
Domanda
Ho questi oggetti di trasferimento di dati:
public class Report
{
public int Id { get; set; }
public int ProjectId { get; set; }
//and so on for many, many properties.
}
Non ho voglia di scrivere
public bool areEqual(Report a, Report b)
{
if (a.Id != b.Id) return false;
if (a.ProjectId != b.ProjectId) return false;
//Repeat ad nauseum
return true;
}
C'è un modo più veloce per verificare se due oggetti con solo le proprietà che hanno gli stessi valori (cosa che non richiedono una riga di codice o di una espressione logica per proprietà?)
Il passaggio a strutture non è un'opzione.
Soluzione
Come su alcuni riflessione, magari utilizzando Expression.Compile()
per le prestazioni?(nota statico ctor qui ci garantisce la compilazione solo una volta per T
):
using System;
using System.Linq.Expressions;
public class Report {
public int Id { get; set; }
public int ProjectId { get; set; }
static void Main() {
Report a = new Report { Id = 1, ProjectId = 13 },
b = new Report { Id = 1, ProjectId = 13 },
c = new Report { Id = 1, ProjectId = 12 };
Console.WriteLine(PropertyCompare.Equal(a, b));
Console.WriteLine(PropertyCompare.Equal(a, c));
}
}
static class PropertyCompare {
public static bool Equal<T>(T x, T y) {
return Cache<T>.Compare(x, y);
}
static class Cache<T> {
internal static readonly Func<T, T, bool> Compare;
static Cache() {
var props = typeof(T).GetProperties();
if (props.Length == 0) {
Compare = delegate { return true; };
return;
}
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
Expression body = null;
for (int i = 0; i < props.Length; i++) {
var propEqual = Expression.Equal(
Expression.Property(x, props[i]),
Expression.Property(y, props[i]));
if (body == null) {
body = propEqual;
} else {
body = Expression.AndAlso(body, propEqual);
}
}
Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
.Compile();
}
}
}
Edit:aggiornato per gestire anche i campi:
static class MemberCompare
{
public static bool Equal<T>(T x, T y)
{
return Cache<T>.Compare(x, y);
}
static class Cache<T>
{
internal static readonly Func<T, T, bool> Compare;
static Cache()
{
var members = typeof(T).GetProperties(
BindingFlags.Instance | BindingFlags.Public)
.Cast<MemberInfo>().Concat(typeof(T).GetFields(
BindingFlags.Instance | BindingFlags.Public)
.Cast<MemberInfo>());
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
Expression body = null;
foreach(var member in members)
{
Expression memberEqual;
switch (member.MemberType)
{
case MemberTypes.Field:
memberEqual = Expression.Equal(
Expression.Field(x, (FieldInfo)member),
Expression.Field(y, (FieldInfo)member));
break;
case MemberTypes.Property:
memberEqual = Expression.Equal(
Expression.Property(x, (PropertyInfo)member),
Expression.Property(y, (PropertyInfo)member));
break;
default:
throw new NotSupportedException(
member.MemberType.ToString());
}
if (body == null)
{
body = memberEqual;
}
else
{
body = Expression.AndAlso(body, memberEqual);
}
}
if (body == null)
{
Compare = delegate { return true; };
}
else
{
Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
.Compile();
}
}
}
}
Altri suggerimenti
Originariamente risposto a ( domanda 1831747 )
Guarda il mio MemberwiseEqualityComparer per vedere se si adatta alle tue esigenze.
E 'davvero facile da usare e molto efficace troppo. Esso utilizza IL-emettono per generare l'intero Risultato e funzione GetHashCode sulla prima corsa (una volta per ogni tipo utilizzato). Si confronterà ogni campo (privato o pubblico) dell'oggetto data utilizzando il confronto di uguaglianza predefinito per quel tipo (EqualityComparer.Default). Abbiamo usato in produzione per un po 'e sembra stabile ma lascio garanzie =)
Si prende cura di tutte quelle pescy edge-casi che raramente si pensa quando si sta rotolando il proprio metodo equals (vale a dire, non si può di confronto il proprio oggetto con null meno che non hai inscatolato in un oggetto prima e il lotto di fuori questioni più relativi a NULL).
Ho intenzione di scrivere un post su di esso, ma non ho ancora ottenuto intorno ad esso. Il codice è un po 'documentato, ma se ti piace ho potuto ripulire un po'.
public override int GetHashCode()
{
return MemberwiseEqualityComparer<Foo>.Default.GetHashCode(this);
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
return Equals(obj as Foo);
}
public override bool Equals(Foo other)
{
return MemberwiseEqualityComparer<Foo>.Default.Equals(this, other);
}
Il MemberwiseEqualityComparer è rilasciato sotto la licenza MIT meaining si può fare più o meno quello che vuoi con esso, incluso l'utilizzo in soluzioni proprietarie senza di te cambiare licenza un po '.
ho esteso il codice di Marc per essere un'implementazione a pieno titolo IEqualityComparer per i miei usi, e pensato che questo può essere utile per altri in futuro:
/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that compares the values of each public property.
/// </summary>
/// <typeparam name="T"> The type to compare. </typeparam>
public class PropertyEqualityComparer<T> : IEqualityComparer<T>
{
// http://stackoverflow.com/questions/986572/hows-to-quick-check-if-data-transfer-two-objects-have-equal-properties-in-c/986617#986617
static class EqualityCache
{
internal static readonly Func<T, T, bool> Compare;
static EqualityCache()
{
var props = typeof(T).GetProperties();
if (props.Length == 0)
{
Compare = delegate { return true; };
return;
}
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
Expression body = null;
for (int i = 0; i < props.Length; i++)
{
var propEqual = Expression.Equal(
Expression.Property(x, props[i]),
Expression.Property(y, props[i]));
if (body == null)
{
body = propEqual;
}
else
{
body = Expression.AndAlso(body, propEqual);
}
}
Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y).Compile();
}
}
/// <inheritdoc/>
public bool Equals(T x, T y)
{
return EqualityCache.Compare(x, y);
}
static class HashCodeCache
{
internal static readonly Func<T, int> Hasher;
static HashCodeCache()
{
var props = typeof(T).GetProperties();
if (props.Length == 0)
{
Hasher = delegate { return 0; };
return;
}
var x = Expression.Parameter(typeof(T), "x");
Expression body = null;
for (int i = 0; i < props.Length; i++)
{
var prop = Expression.Property(x, props[i]);
var type = props[i].PropertyType;
var isNull = type.IsValueType ? (Expression)Expression.Constant(false, typeof(bool)) : Expression.Equal(prop, Expression.Constant(null, type));
var hashCodeFunc = type.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public);
var getHashCode = Expression.Call(prop, hashCodeFunc);
var hashCode = Expression.Condition(isNull, Expression.Constant(0, typeof(int)), getHashCode);
if (body == null)
{
body = hashCode;
}
else
{
body = Expression.ExclusiveOr(Expression.Multiply(body, Expression.Constant(typeof(T).AssemblyQualifiedName.GetHashCode(), typeof(int))), hashCode);
}
}
Hasher = Expression.Lambda<Func<T, int>>(body, x).Compile();
}
}
/// <inheritdoc/>
public int GetHashCode(T obj)
{
return HashCodeCache.Hasher(obj);
}
}
Purtroppo si sta andando ad avere per scrivere il metodo per confrontare i valori del campo. System.ValueType
è costruito per utilizzare la riflessione e confrontare i valori di campo di un struct
ma anche questo è sconsigliabile dovuto al rallentamento delle prestazioni. La cosa migliore da fare è eseguire l'override del metodo Equals
e anche implementare il IEquatable<T>
interfaccia per un sovraccarico di Equals
fortemente tipizzato.
Mentre si è in esso, si potrebbe anche fornire un buon esclusione GetHashCode
nonché per completare l'attuazione Equals
. Tutti questi passaggi sono considerati buone pratiche.
Sarà necessario utilizzare la riflessione per fare questo, segui questo link -> Confrontando oggetto proprietà in C #