Como verificar rapidamente se objetos de transferência de dois dados têm propriedades iguais em C #?

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

  •  13-09-2019
  •  | 
  •  

Pergunta

Eu tenho esses transferência de dados objetos:

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

Eu não quero escrever

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

Existe uma maneira mais rápida para testar se dois objetos com apenas propriedades têm os mesmos valores (algo que não requer uma linha de código ou de uma expressão lógica por propriedade?)

Mudar para estruturas não é uma opção.

Foi útil?

Solução

Como sobre alguma reflexão, talvez usando Expression.Compile() para o desempenho? (Note o ctor estática aqui garante que só compilá-lo uma vez por 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: atualizado para campos punho demasiado:

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

Outras dicas

Originalmente respondeu em ( Pergunta 1831747 )

Confira o meu MemberwiseEqualityComparer para ver se ele se adapta às suas necessidades.

É muito fácil de usar e bastante eficiente também. Ele usa IL-emita para gerar os Iguais inteiras e função GetHashCode na primeira execução (uma vez para cada tipo usado). Ele irá comparar cada campo (privada ou pública) do objeto determinado usando o comparador de igualdade padrão para esse tipo (EqualityComparer.Default). Temos vindo a usá-lo em produção por um tempo e parece estável, mas eu vou deixar nenhuma garantia =)

Ela cuida de todos os Edge-casos os pescy que raramente pensa quando você está rolando o seu próprio método equals (ou seja, você não pode comparer seu próprio objeto com nulo, a menos que você tenha em caixa-lo em um objeto primeiro e lote está fora mais problemas nulos-relacionada).

Eu estive significado para escrever um post sobre isso, mas não tivemos tempo para isso ainda. O código é um pouco situação irregular, mas se você como se eu pudesse limpá-lo um pouco.

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

O MemberwiseEqualityComparer é liberado sob a MIT licença meaining você pode fazer praticamente o que quiser com ele, incluindo usá-lo em soluções proprietárias, sem alterar-lhe licenciar um pouco.

Eu tenho estendido código de Marc para ser uma implementação de IEqualityComparer de pleno direito para os meus próprios usos, e achei que este pode ser útil para outros no 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);
    }
}

Infelizmente você vai ter que escrever o método para comparar os valores de campo. System.ValueType é construído para uso reflexão e comparar os valores de campo de um struct mas mesmo isso é desaconselhável devido ao desempenho lento. A melhor coisa a fazer é substituir o método Equals e também implementar o IEquatable<T> interface para uma sobrecarga de Equals fortemente tipado.

Enquanto você está nisso, você também pode fornecer uma boa substituição GetHashCode bem para complementar a implementação Equals. Todas essas etapas são consideradas boas práticas.

Você vai precisar de usar o reflexo para fazer isso, siga este link -> objeto Comparando Propriedades em c #

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top