Wie schnell überprüfen, ob zwei Datentransferobjekte gleich Eigenschaften in C #?

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

  •  13-09-2019
  •  | 
  •  

Frage

Ich habe diese Datentransferobjekte:

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

Ich will nicht schreiben

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

Gibt es einen schnelleren Weg zu testen, ob zwei Objekt mit nur Eigenschaften die gleichen Werte hat (etwas, das nicht eine Zeile Code oder einen logischen Ausdruck pro Objekt benötigt?)

Umschalten auf structs ist keine Option.

War es hilfreich?

Lösung

Wie wäre es mit Reflexion, vielleicht Expression.Compile() für Leistung durch? (Beachten Sie die statische Ctor hier garantiert, dass wir es nur einmal kompilieren pro 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: aktualisierten Felder zu handhaben zu:

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

Andere Tipps

Ursprünglich bei ( Frage 1831747 )

Sehen Sie sich meine MemberwiseEqualityComparer zu sehen, ob es Ihren Bedürfnissen entspricht.

Es ist wirklich einfach zu bedienen und auch recht effizient. Es verwendet IL-emittieren die gesamte Equals und GetHashCode Funktion auf dem ersten Durchgang (einmal für jeden Typ verwendet wird) zu erzeugen. Es wird jedes Feld (privat oder öffentlich) des gegebenen Objekts vergleichen Sie den Standardgleichheitsvergleich für diesen Typen (EqualityComparer.Default) verwendet wird. Wir haben es in der Produktion für eine Weile und es scheint stabil, aber ich werde keine Garantien verlassen =)

Es kümmert sich um alle jene pescy kanten Fälle, die Sie selten denken, wenn Sie Ihre eigene Methode equals sind Walzen (dh, können Sie nicht Ihr eigenes Objekt mit null COMPARER, wenn Sie es in einem Objekt geboxt habe zuerst und viele der aus mehr null-Fragen).

Ich habe Sinn, eine Blog-Post, darüber zu schreiben, aber noch nicht um, um es noch bekommen. Der Code ist ein bisschen ohne Papiere, aber wenn Sie es, wie ich es reinigen könnte ein wenig.

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

Die MemberwiseEqualityComparer wird unter dem MIT-Lizenz meaining Sie ziemlich viel tun können, was Sie wollen mit ihm, auch in proprietären Lösungen verwenden, ohne dass Sie die Lizenzierung ein wenig zu verändern.

Ich habe Marcs Code erweiterte eine vollwertiges IEqualityComparer Implementierung für meine eigenen Anwendungen zu sein, und dachte, das zu anderen in der Zukunft nützlich sein kann:

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

Leider sind Sie gehen zu müssen, die Methode schreiben, um die Feldwerte zu vergleichen. System.ValueType gebaut Reflexion zu nutzen und die Feldwert eines struct zu vergleichen, aber auch das ist nicht ratsam aufgrund zu geringer Leistung. Das Beste, was zu tun ist, um die Equals Methode außer Kraft zu setzen und implementieren auch die IEquatable<T> Schnittstelle für eine stark typisierte Equals Überlastung.

Wenn Sie schon dabei sind, könnten Sie bieten auch eine gute GetHashCode Überschreibung sowie die Equals Implementierung zu ergänzen. Alle diese Schritte werden als gute Praxis.

Sie müssen Reflexion verwenden, um dies zu tun, folgen Sie bitte diesem Link -> Objekt Vergleich Eigenschaften in c #

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top