Comment vérifier rapidement si deux objets de données de transfert ont des propriétés égales en C #?

StackOverflow https://stackoverflow.com/questions/986572

  •  13-09-2019
  •  | 
  •  

Question

J'ai ces objets de transfert de données:

public class Report 
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    //and so on for many, many properties.
}

Je ne veux pas écrire

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

Y at-il un moyen plus rapide pour tester si deux objets ayant des propriétés que possèdent les mêmes valeurs (ce qui ne nécessite pas une ligne de code ou une expression logique par propriété?)

Le passage à struct est pas une option.

Était-ce utile?

La solution

Que diriez-vous de réflexion, en utilisant peut-être Expression.Compile() pour la performance? (Notez le cteur statique ici assure que nous recueillons seulement une fois par 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: mise à jour pour traiter les champs trop:

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();
            }
        }
    }
}

Autres conseils

A l'origine une réponse à ( question 1831747 )

Regarde mes MemberwiseEqualityComparer pour voir si elle répond à vos besoins.

Il est vraiment facile à utiliser et très efficace aussi. Il utilise l'IL-émettre pour générer l'ensemble des pairs et la fonction GetHashCode sur la première course (une fois pour chaque type utilisé). Il comparera chaque champ (privé ou public) de l'objet donné en utilisant le comparateur d'égalité par défaut pour ce type (EqualityComparer.Default). Nous l'avons utilisé dans la production pendant un certain temps et il semble stable, mais je vais laisser aucune garantie =)

Il prend soin de tous les pescy bord des cas que vous pensez rarement quand vous rouler votre propre méthode equals (c.-à-vous ne pouvez pas comparateur votre propre objet avec nul, sauf si vous avez mis en boîte dans un premier objet et beaucoup de problèmes de plus liés null).

Je voulais écrire un billet de blog à ce sujet, mais ne l'ai pas eu l'occasion de encore. Le code est un peu en situation irrégulière, mais si vous aimez je pouvais nettoyer un peu.

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);
}

Le MemberwiseEqualityComparer est libéré sous la licence MIT meaining vous pouvez faire à peu près tout ce que vous voulez avec elle, y compris l'utilisation dans des solutions propriétaires sans vous changer de licence un peu.

J'ai étendu le code de Marc pour une mise en œuvre IEqualityComparer à part entière pour mes propres usages, et je pensais que cela peut être utile à d'autres à l'avenir:

/// <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);
    }
}

Malheureusement, vous allez avoir à écrire la méthode pour comparer les valeurs de champ. System.ValueType est conçu pour utiliser la réflexion et comparer les valeurs de champ d'un struct mais même cela est à déconseiller en raison de ralentissement des performances. La meilleure chose à faire est de remplacer la méthode de Equals et aussi mettre en œuvre le IEquatable<T> interface pour une surcharge de Equals fortement typé.

Pendant que vous y êtes, vous pourriez aussi bien fournir une bonne override GetHashCode et pour compléter la mise en œuvre de Equals. Toutes ces étapes sont considérées comme bonnes pratiques.

Vous devrez utiliser la réflexion pour ce faire, s'il vous plaît suivez ce lien -> comparaison objet propriétés en c #

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top