Подтвердить что ты не робот

Хочешь быстро проверить, имеют ли данные данные два объекта с равными свойствами в С#?

У меня есть объекты объектов передачи данных:

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

Я не хочу писать

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

Есть ли более быстрый способ проверить, если объект с только свойствами имеет одинаковые значения (что не требует одной строки кода или одного логического выражения для свойства?)

Переключение на структуры не является вариантом.

4b9b3361

Ответ 1

Как насчет некоторого отражения, возможно, используя Expression.Compile() для производительности? (обратите внимание, что статический ctor здесь гарантирует, что мы только скомпилируем его один раз за 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();
        }
    }
}

Изменить: обновлено для обработки полей тоже:

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

Ответ 2

Первоначально ответил на (вопрос 1831747)

Посмотрите мой MemberwiseEqualityComparer, чтобы узнать, соответствует ли он вашим потребностям.

Он очень прост в использовании и достаточно эффективен. Он использует IL-emit для генерации всех функций Equals и GetHashCode при первом запуске (один раз для каждого используемого типа). Он будет сравнивать каждое поле (личное или общедоступное) данного объекта, используя сопоставитель равенства по умолчанию для этого типа (EqualityComparer.Default). Мы использовали его в производстве на некоторое время, и он кажется стабильным, но я не оставлю никаких гарантий =)

Он заботится обо всех тех крайних случаях pescy, о которых вы редко думаете, когда вы переворачиваете свой собственный метод equals (т.е. вы не можете сравнивать свой собственный объект с нулевым значением, если только вы не ввели его в первый объект и много больше проблем, связанных с нулем).

Я имел в виду написать сообщение в блоге об этом, но еще не добрался до него. Код немного недокументирован, но если вам это нравится, я мог бы немного его очистить.

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

MemberwiseEqualityComparer выпущен под лицензией MIT, и вы можете делать все, что захотите, в том числе, используя его в собственных решениях без немного изменив лицензию.

Ответ 3

Я расширил код Marc, чтобы быть полноценной реализацией IEqualityComparer для своих собственных целей, и подумал, что это может быть полезно для других в будущем:

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

Ответ 4

К сожалению, вам придется написать метод для сравнения значений полей. System.ValueType построен для использования отражения и сравнения значений полей a struct, но даже это нецелесообразно из-за низкой производительности. Лучше всего переопределить метод Equals, а также реализовать IEquatable<T> интерфейс для сильно типизированной перегрузки Equals.

В то время как вы на нем, вы также можете обеспечить хорошее GetHashCode переопределение, а также дополнение к реализации Equals. Все эти шаги считаются хорошей практикой.