Comparando propriedades de objeto em c # [fechado]


111

Isso é o que eu criei como um método em uma classe herdada por muitas das minhas outras classes. A ideia é que ele permita a comparação simples entre propriedades de objetos do mesmo tipo.

Agora, isso funciona - mas no interesse de melhorar a qualidade do meu código, pensei em jogá-lo fora para análise. Como pode ser melhor / mais eficiente / etc.?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}


3
Pela maneira você está ciente deste site SE: codereview.stackexchange.com
wip

Existem algumas bibliotecas de comparação de objetos: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... e uma tonelada de implementadores de comparação de igualdade genéricos, alguns dos quais são: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . O último grupo pode ser limitado em escopo e flexibilidade quanto ao que eles podem alcançar.
nawfal

Estou votando para fechar esta questão como fora do tópico porque ela pertence à revisão de código
Xiaoy312

Respostas:


160

Eu estava procurando um trecho de código que faria algo semelhante para ajudar a escrever o teste de unidade. Aqui está o que acabei usando.

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

EDITAR:

Mesmo código acima, mas usa métodos LINQ e extensão:

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }

Big T - bastante antigo, mas definitivamente serve a um grande propósito tanto para testes quanto para comparações simples .. obrigado +1
jim tollan

1
Isso é bom, mas descobri que não funciona com objetos mais complexos. Por exemplo, eu tenho um objeto com algumas strings (ele os compara bem), mas esse objeto também tem uma lista de outro objeto, que não compara corretamente, então preciso recursar isso de alguma forma.
Ryan Thomas

1
Eu tive que adicionar aos critérios no primeiro, onde mais dois critérios, porque você tem que excluir propriedades indexadas que geram exceção em outro caso. Aqui estão os critérios para este erro: pi.GetIndexParameters (). Length == 0. E o segundo critério para resolver o problema declarado por @RyanThomas é este: pi.GetUnderlyingType (). IsSimpleType (). Como você verá, IsSimpleType é uma extensão que não existe para a classe Type. Modifiquei a resposta para adicionar todas essas condições e a extensão.
Samuel

64

ATUALIZAÇÃO: A versão mais recente do Compare-Net-Objects está localizada no GitHub , tem o pacote NuGet e o Tutorial . Pode ser chamado de

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

Ou se você precisar alterar alguma configuração, use

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

A lista completa de parâmetros configuráveis ​​está em ComparisonConfig.cs

Resposta original:

As limitações que vejo em seu código:

  • O maior deles é que ele não faz uma comparação profunda de objetos.

  • Ele não faz uma comparação elemento por elemento no caso de propriedades serem listas ou contêm listas como elementos (isso pode ir em n níveis).

  • Não leva em consideração que alguns tipos de propriedades não devem ser comparados (por exemplo, uma propriedade Func usada para fins de filtragem, como a da classe PagedCollectionView).

  • Ele não controla quais propriedades realmente eram diferentes (para que você possa mostrar em suas asserções).

Eu estava procurando hoje por alguma solução para fins de teste de unidade para fazer comparação profunda propriedade por propriedade e acabei usando: http://comparenetobjects.codeplex.com .

É uma biblioteca gratuita com apenas uma classe que você pode simplesmente usar assim:

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

Além disso, pode ser facilmente recompilado para o Silverlight. Basta copiar uma classe em um projeto Silverlight e remover uma ou duas linhas de código para comparações que não estão disponíveis no Silverlight, como comparação de membros privados.


2
Liviu, reparei no seu comentário sobre a classe não ser compatível com o Silverlight. Acabei de alterá-lo para ser compatível com Silverlight e Windows Phone 7. Obtenha o mais recente. Veja o conjunto de mudanças 74131 em comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer

Isso parece promissor. Vou experimentar
DJ Burb

Obrigado pelo ótimo exemplo! Além disso, a IgnoreObjectTypesconfiguração pode ser útil quando existem diferentes tipos.
Sergey Brunov,

A versão 2.0 tem uma versão da Biblioteca de Classes Portátil compatível com Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS e Xamarin Droid
Greg Finzer

DifferencesStringfoi descontinuado na classe CompareObjects. Mas agora você pode obter isso no ComparisonResult:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze,

6

Acho que seria melhor seguir o padrão para Substituir Objeto # Equals ()
Para uma descrição melhor: Leia o C # Efetivo de Bill Wagner - Item 9, eu acho

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • Também em métodos que verificam a igualdade, você deve retornar verdadeiro ou falso. eles são iguais ou não são .. em vez de lançar uma exceção, retorna falso.
  • Eu consideraria substituir Object # Equals.
  • Mesmo que você deva ter considerado isso, usar o Reflection para comparar propriedades é supostamente lento (não tenho números para comprovar isso). Este é o comportamento padrão para valueType # Equals em C # e é recomendado que você substitua Equals para tipos de valor e faça uma comparação de desempenho. (Anteriormente, li isso rapidamente, pois você tem uma coleção de objetos de propriedade personalizados ... meu mal.)

Atualização - dezembro de 2011:

  • Obviamente, se o tipo já tiver um Equals () de produção, você precisará de outra abordagem.
  • Se você estiver usando isso para comparar estruturas de dados imutáveis ​​exclusivamente para fins de teste, você não deve adicionar um Equals às classes de produção (alguém pode fazer os testes seguindo a implementação Equals ou você pode impedir a criação de uma implementação Equals exigida pela produção) .

Tive problemas ao substituir .Equals () porque estou tentando implementar isso em uma classe base que é herdada ... porque não sei as chaves para a classe em que será executado, não posso implementar uma substituição decente para GetHasCode () (necessária quando você substituir Equals ()).
nailitdown

O requisito é que se objA.Equals (objB) então objA.GetHashCode () == objB.GetHashCode (). GetHashCode não deve ser dependente do estado / dados mutáveis ​​de uma classe ... Eu não entendi o que você quis dizer com chaves para a classe .. Parece algo que pode ser resolvido. O tipo de base não tem as 'chaves'?
Gishu

6

Se o desempenho não importa, você pode serializá-los e comparar os resultados:

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();

4
tentei fazer isso um tempo atrás, você se perguntaria quantos objetos não são serializáveis ​​...
Offler

5

Acho que a resposta do Big T foi muito boa, mas faltou uma comparação profunda, então ajustei um pouco:

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}

4

Eu adicionaria a seguinte linha ao método PublicInstancePropertiesEqual para evitar erros de copiar e colar:

Assert.AreNotSame(self, to);

2

Você substitui .ToString () em todos os seus objetos que estão nas propriedades? Caso contrário, essa segunda comparação poderia retornar com null.

Além disso, nessa segunda comparação, estou em dúvida quanto à construção de! (A == B) em comparação com (A! = B), em termos de legibilidade daqui a seis meses / dois anos. A linha em si é bem larga, o que é bom se você tiver um monitor largo, mas pode não imprimir muito bem. (nitpick)

Todos os seus objetos estão sempre usando propriedades para que esse código funcione? Pode haver alguns dados internos não proprietários que podem ser diferentes de um objeto para outro, mas todos os dados expostos são os mesmos? Estou pensando em alguns dados que podem mudar ao longo do tempo, como dois geradores de números aleatórios que acertam o mesmo número em um ponto, mas vão produzir duas sequências diferentes de informações ou apenas quaisquer dados que não sejam expostos por meio da interface de propriedade.


bons pontos -! = ... concordo, ponto aceito. ToString () foi uma tentativa de contornar .GetValue retornando um objeto (portanto, a comparação é sempre falsa, pois é uma comparação de referência) .. existe uma maneira melhor?
Nailitdown

Se GetValue estiver retornando um objeto, você pode percorrer novamente essa função? ou seja, chamar PropertiesEqual nos objetos retornados?
mmr

1

Se você está apenas comparando objetos do mesmo tipo ou mais abaixo na cadeia de herança, por que não especificar o parâmetro como seu tipo base, em vez de objeto?

Também faça verificações nulas no parâmetro.

Além disso, eu faria uso de 'var' apenas para tornar o código mais legível (se for um código c # 3)

Além disso, se o objeto tiver tipos de referência como propriedades, você está apenas chamando ToString () neles, o que realmente não compara valores. Se ToString não for substituído, ele apenas retornará o nome do tipo como uma string que pode retornar falsos positivos.


bom ponto sobre os tipos de referência - no meu caso, não importa, mas há uma boa chance de que sim.
Nailitdown

1

A primeira coisa que eu sugeriria seria dividir a comparação real para que seja um pouco mais legível (também retirei o ToString () - isso é necessário?):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

A próxima sugestão seria minimizar o uso de reflexão tanto quanto possível - é muito lento. Quer dizer, muito lento. Se você for fazer isso, sugiro armazenar em cache as referências de propriedade. Não estou intimamente familiarizado com a Reflection API, então, se ela estiver um pouco errada, apenas ajuste para compilar:

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

No entanto, devo dizer que concordo com os outros cartazes. Isso tem um cheiro preguiçoso e ineficiente. Você deve implementar IComparable ao invés :-).


Eu estava apenas olhando para IComparable, mas parecia que era para classificar e ordenar .. é realmente útil para comparar a igualdade de dois objetos?
Nailitdown

Absolutamente, porque .Equals (objeto o) é definido como this.CompareTo (o) == 0. Assim, equals usa ComparesTo () para determinar a igualdade. Isso será muito mais eficiente (e prática padrão) do que usar reflexão.
tsimon

Posso estar enganado supondo que Equals é implementado (ou deveria ser implementado) com referência a CompareTo (). Você deve considerar substituir Equals conforme descrito aqui: stackoverflow.com/questions/104158/…
tsimon

1

aqui é revisado um para tratar null = null como igual

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }

E se eu tivesse um gráfico de objeto profundo, qual é a melhor maneira de usar acima para retornar uma lista de propriedades novas e antigas que foram alteradas?
Rod

1

Acabei fazendo isso:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

Uso:

    if (Compare<ObjectType>(a, b))

Atualizar

Se você deseja ignorar algumas propriedades pelo nome:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

Uso:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))

1

Você pode otimizar seu código chamando GetProperties apenas uma vez por tipo:

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}

1

Para completar, desejo adicionar referência a http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection. Ele tem uma lógica mais completa do que a maioria das outras respostas nesta página.

No entanto eu prefiro Compare-Net-Objects biblioteca https://github.com/GregFinzer/Compare-Net-Objects (referido por Liviu Trifoi 's resposta )
A biblioteca tem pacote NuGet http://www.nuget.org/packages/ CompareNETObjects e várias opções para configurar.


1

Certifique-se de que os objetos não sejam null.

Tendo obj1e obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );

e se ambos forem nulos? então eles não são iguais?
mmr

bom ponto sobre nulos, no meu caso, usar .Equals () não parece funcionar, e é por isso que eu vim com esta solução
nailitdown

bem, o caso que estou testando é de dois objetos, um recém-criado e um da sessão. comparar os dois com .Equals () retorna falso, embora ambos tenham valores de propriedade idênticos
nailitdown

0

Isso funciona mesmo se os objetos forem diferentes. você pode personalizar os métodos na classe de utilitários, talvez você queira comparar propriedades privadas também ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}

Esse código não é 100% eficiente. não funciona em algumas situações, por exemplo, se contiver uma propriedade do tipo objeto.
Tono Nam

0

Atualização da resposta de Liviu acima - CompareObjects.DifferencesString foi descontinuado.

Isso funciona bem em um teste de unidade:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
É ótimo que você corrigiu a depreciação, mas acho que essa resposta deveria ser um comentário na resposta do Liviu. Especialmente porque seu código de amostra (em comparação com o de Liviu) não possui os parâmetros de CompareLogic (que tenho certeza que são importantes), e também a mensagem de assert (que estava obsoleta). A afirmação pode ser corrigida com:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze 04/10/2014

0

Este método obterá propertiesda classe e comparará os valores de cada uma property. Se algum dos valores for diferente, será return false, do contrário será return true.

public static bool Compare<T>(T Object1, T object2)
{
    //Get the type of the object
    Type type = typeof(T);

    //return false if any of the object is false
    if (Object1 == null || object2 == null)
        return false;

    //Loop through each properties inside class and get values for the property from both the objects and compare
    foreach (System.Reflection.PropertyInfo property in type.GetProperties())
    {
        if (property.Name != "ExtensionData")
        {
            string Object1Value = string.Empty;
            string Object2Value = string.Empty;
            if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
            if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
            if (Object1Value.Trim() != Object2Value.Trim())
            {
                return false;
            }
        }
    }
    return true;
}

Uso:

bool isEqual = Compare<Employee>(Object1, Object2)


0

Para expandir a resposta de @nawfal: s, eu uso isso para testar objetos de diferentes tipos em meus testes de unidade para comparar nomes de propriedades iguais. No meu caso, entidade de banco de dados e DTO.

Usado assim no meu teste;

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}

0

às vezes você não deseja comparar todas as propriedades públicas e deseja comparar apenas o subconjunto delas, então, neste caso, você pode apenas mover a lógica para comparar a lista desejada de propriedades para a classe abstrata

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

e usar esta classe abstrata mais tarde para comparar os objetos

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}

0

minha solução inspirada na resposta de Aras Alenin acima, onde adicionei um nível de comparação de objetos e um objeto personalizado para resultados de comparação. Também estou interessado em obter o nome da propriedade com o nome do objeto:

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

Usando a seguinte classe para armazenar resultados de comparação

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

E um teste de unidade de amostra:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.