Usando ObjectsComparer para comparar objetos no .net

Valerii Tereshchenko Segue 18 de jul · 12 min ler Foto de Dietmar Becker em Unsplash

A estrutura de Comparador de Objetos fornece mecanismo para comparar recursivamente objetos complexos por propriedades (suporta matrizes, listas, diferentes tipos de objetos dinâmicos e mais), permite substituir regras de comparação para propriedades e tipos específicos.

Introdução

É uma situação bastante comum quando objetos complexos precisam ser comparados. Às vezes, os objetos podem conter elementos aninhados ou alguns membros devem ser excluídos da comparação (identificadores gerados automaticamente, data de criação / atualização etc.) ou alguns membros podem ter regras de comparação personalizadas (mesmos dados em formatos diferentes, como números de telefone). Esse pequeno framework foi desenvolvido para resolver esse tipo de problema.

Resumidamente, o Comparador de Objetos é um comparador objeto-a-objeto, que permite comparar objetos recursivamente membro por membro e definir regras de comparação customizadas para certas propriedades, campos ou tipos.

O Objects Comparer pode ser usado em projetos .Net Core (.Net Standard 1.6, 2.0 ou superior) e em projetos .Net (versão 4.5 ou superior).

Objetos Suportados:

  • Tipos primitivos
  • Classes e estruturas
  • Enumeráveis (matrizes, coleções, listas, dicionários, etc.)
  • Matrizes multidimensionais
  • Enumerações
  • Bandeiras
  • Objetos dinâmicos ( ExpandoObject , DynamicObject e objetos dinâmicos gerados pelo compilador).

Instalação

O Objects Comparer pode ser instalado como pacote NuGet.

Install-Package ObjectsComparer

Código fonte

O código fonte pode ser encontrado no GitHub .

Exemplos básicos

Para mostrar como usar o Objects Comparer, vamos criar duas classes

 classe pública ClassA 
{
string pública StringProperty {get; conjunto; }
Public int IntProperty {get; conjunto; } public SubClassA SubClass {get; conjunto; }
}
classe pública SubClassA
{
bool público BoolProperty {get; conjunto; }
}

Há alguns exemplos abaixo, como o Object Comparer pode ser usado para comparar instâncias dessas classes.

Esconder o código da cópia

 // Inicializa objetos e comparador 
var a1 = new ClassA {StringProperty = "String", IntProperty = 1};
var a2 = new ClassA {StringProperty = "String", IntProperty = 1};
var comparer = new Comparer <ClassA> ();
// Comparar objetos
Diferenças IEnumerable <Difference>;
var isEqual = comparer.Compare (a1, a2, out diferenças);
// Imprimir resultados
Debug.WriteLine (isEqual? "Objetos são iguais": string.Join (Environment.NewLine, differenses));

Objects are equal

Nos exemplos abaixo, Comparar objetos e Imprimir blocos de resultados serão pulados para brevidade, exceto alguns casos.

Esconder o código da cópia

 var a1 = new ClassA {StringProperty = "String", IntProperty = 1}; 
var a2 = new ClassA {StringProperty = "String", IntProperty = 2};
var comparer = new Comparer <ClassA> ();

Difference: DifferenceType=ValueMismatch, MemberPath='IntProperty', Value1='1', Value2='2'.

 var a1 = novo ClassA {SubClass = novo SubClassA {BoolProperty = true}}; 
var a2 = novo ClassA {SubClass = novo SubClassA {BoolProperty = false}};
var comparer = new Comparer <ClassA> ();

Difference: DifferenceType=ValueMismatch, MemberPath='SubClass.BoolProperty', Value1='True', Value2='False'.

 var a1 = novo StringBuilder ("abc"); 
var a2 = novo StringBuilder ("abd");
var comparer = new Comparer <StringBuilder> ();

Difference: DifferenceType=ValueMismatch, MemberPath='', Value1='abc', Value2='abd'.

Enumeráveis (matrizes, coleções, listas, etc.)

Nesse caso, os enumeráveis podem ter um número diferente de elementos ou alguns elementos podem ter valores diferentes. Enumeráveis podem ser genéricos ou não genéricos. No caso de enumeráveis não genéricos, os elementos com o mesmo índice serão comparados se os tipos desses elementos forem iguais, senão a diferença com DifferenceType=TypeMismatch será adicionado à lista de diferenças.

 var a1 = new [] {1, 2, 3}; 
var a2 = new [] {1, 2, 3};
var comparer = new Comparer <int []> ();

Objects are equal

 var a1 = novo [] {1, 2}; 
var a2 = new [] {1, 2, 3};
var comparer = new Comparer <int []> ();

Difference: DifferenceType=ValueMismatch, MemberPath='Length', Value1='2', Value2='3'.

 var a1 = new [] {1, 2, 3}; 
var a2 = new [] {1, 4, 3};
var comparer = new Comparer <int []> ();

Difference: DifferenceType=ValueMismatch, MemberPath='[1]', Value1='2', Value2='4'.

 var a1 = novo ArrayList {"Str1", "Str2"}; 
var a2 = new ArrayList {"Str1", 5};
var comparer = new Comparer <ArrayList> ();

Difference: DifferenceType=TypeMismatch, MemberPath='[1]', Value1='Str2', Value2='5'.

Conjuntos

 var a1 = new [] {1, 2, 3}; 
var a2 = new [] {1, 2, 3};
var comparer = new Comparer <int []> ();

Objects are equal

 var a1 = novo HashSet <int> {1, 2, 3}; 
var a2 = novo HashSet <int> {2, 1, 4};
var comparer = new Comparador <HashSet <int >> ();

Difference: DifferenceType=MissedElementInSecondObject, MemberPath='', Value1='3', Value2=''.<br /> Difference: DifferenceType=MissedElementInFirstObject, MemberPath='', Value1='', Value2='4'.

Matrizes multidimensionais

 var a1 = novo [] {novo [] {1, 2}}; 
var a2 = novo [] {novo [] {1, 3}};
var comparer = new Comparer <int [] []> ();

Difference: DifferenceType=ValueMismatch, MemberPath='[0][1]', Value1='2', Value2='3'.

 var a1 = novo [] {novo [] {1, 2}}; 
var a2 = novo [] {novo [] {2, 2}, novo [] {3, 5}};
var comparer = new Comparer <int [] []> ();

Difference: DifferenceType=ValueMismatch, MemberPath='Length', Value1='1', Value2='2'.

 var a1 = novo [] {novo [] {1, 2}, novo [] {3, 5}}; 
var a2 = novo [] {novo [] {1, 2}, novo [] {3, 5, 6}};
var comparer = new Comparer <int [] []> ();

Difference: DifferenceType=ValueMismatch, MemberPath='[1].Length', Value1='2', Value2='3'.

 var a1 = novo [,] {{1, 2}, {1, 3}}; 
var a2 = novo [,] {{1, 3, 4}, {1, 3, 8}};
var comparer = new Comparer <int [,]> ();

Difference: DifferenceType=ValueMismatch, MemberPath='Dimension1', Value1='2', Value2='3'.

 var a1 = novo [,] {{1, 2}}; 
var a2 = novo [,] {{1, 3}};
var comparer = new Comparer <int [,]> ();

Difference: DifferenceType=ValueMismatch, MemberPath='[0,1]', Value1='2', Value2='3'.

Objetos dinâmicos

O C # suporta vários tipos de objetos, cujos membros podem ser adicionados e removidos dinamicamente em tempo de execução.

ExpandoObject

Se você não estiver familiarizado com o uso do ExpandoObject você pode ler isto ou procurar outro exemplo.

 dinâmico a1 = new ExpandoObject (); 
a1.Field1 = "A";
a1.Fundo2 = 5;
a1.Field4 = 4;
dynamic a2 = novo ExpandoObject ();
a2.Field1 = "B";
a2.Field3 = false;
a2.Field4 = "C";
var comparer = new Comparer ();

Difference: DifferenceType=ValueMismatch, MemberPath='Field1', Value1='A', Value2='B'.

Difference: DifferenceType=MissedMemberInSecondObject, MemberPath='Field2', Value1='5', Value2=''.

Difference: DifferenceType=TypeMismatch, MemberPath='Field4', Value1='4', Value2='C'.

Difference: DifferenceType=MissedMemberInFirstObject, MemberPath='Field3', Value1='', Value2='False'.

 dinâmico a1 = new ExpandoObject (); 
a1.Field1 = "A";
a1.Fundo2 = 5;
dynamic a2 = novo ExpandoObject ();
a2.Field1 = "B";
a2.Field3 = false;
var comparer = new Comparer ();

Difference: DifferenceType=ValueMismatch, MemberPath='Field1', Value1='A', Value2='B'.

Difference: DifferenceType=MissedMemberInSecondObject, MemberPath='Field2', Value1='5', Value2=''.

Difference: DifferenceType=MissedMemberInFirstObject, MemberPath='Field3', Value1='', Value2='False'.

O comportamento se o membro não existir pode ser alterado ao fornecer ComparisonSettings personalizados (consulte Configurações de comparação abaixo).

 dinâmico a1 = new ExpandoObject (); 
a1.Field1 = "A";
a1.Field2 = 0;
dynamic a2 = novo ExpandoObject ();
a2.Field1 = "B";
a2.Field3 = false;
a2.Field4 = "S";
var comparer = new Comparer (novas ComparisonSettings {UseDefaultIfMemberNotExist = true});

Difference: DifferenceType=ValueMismatch, MemberPath='Field1', Value1='A', Value2='B'.

Difference: DifferenceType=ValueMismatch, MemberPath='Field4', Value1='', Value2='S'.

DynamicObject

DynamicObject é uma classe abstrata e não pode ser instanciada diretamente. Vamos supor que tenhamos essa implementação da classe DynamicObject. É necessário ter uma implementação correta do método GetDynamicMemberNames , caso contrário, o Objects Comparer não funcionaria de maneira correta.

Se você não estiver familiarizado com o uso do DynamicObject você pode ler isto ou procurar outro exemplo.

 private class DynamicDictionary: DynamicObject 
{
public int IntProperty {get; conjunto; }
private readonly Dictionary <string, objeto> _dictionary = novo dicionário <string, objeto> (); public override bool TryGetMember (fichário GetMemberBinder, resultado do objeto fora)
{
nome do var = binder.Name;
return _dictionary.TryGetValue (nome, resultado final);
}
public bool TrySetMember (fichário SetMemberBinder, valor do objeto)
{
_dictionary [binder.Name] = valor;
retorno verdadeiro;
}
public override IEnumerable <string> GetDynamicMemberNames ()
{
return _dictionary.Keys;
}
}
dinâmico a1 = new DynamicDictionary ();
a1.Field1 = "A";
a1.Field3 = true;
dinâmica a2 = new DynamicDictionary ();
a2.Field1 = "B";
a2.Field2 = 8;
a2.Field3 = 1;
var comparer = new Comparer ();

Difference: DifferenceType=ValueMismatch, MemberPath='Field1', Value1='A', Value2='B'.

Difference: DifferenceType=TypeMismatch, MemberPath='Field3', Value1='True', Value2='1'.

Difference: DifferenceType=MissedMemberInFirstObject, MemberPath='Field2', Value1='', Value2='8'.

Objetos gerados pelo compilador

Esse tipo de objeto dinâmico é mais popular e mais fácil de criar.

 dinâmica a1 = nova 
{
Campo1 = "A",
Campo 2 = 5,
Field3 = true
};
dinâmica a2 = nova
{
Campo1 = "B",
Campo2 = 8
};
var comparer = new Comparer ();
Diferenças IEnumerable <Difference>;
var isEqual = comparer.Compare ((object) a1, (objeto) a2, out diferenças);

Difference: DifferenceType=ValueMismatch, MemberPath='Field1', Value1='A', Value2='B'.

Difference: DifferenceType=TypeMismatch, MemberPath='Field2', Value1='5', Value2='8'.

Difference: DifferenceType=MissedMemberInSecondObject, MemberPath='Field3', Value1='True', Value2=''.

Este exemplo requer algumas explicações adicionais. Tipos de objetos a1 e a2 foram gerados pelo compilador e são considerados como o mesmo tipo se e somente se os objetos a1 e a2 tiverem o mesmo conjunto de membros (mesmo nome e mesmo tipo). Se a conversão para (objeto) for ignorada no caso de um conjunto diferente de membros, RuntimeBinderException será lançado.

Substituindo Regras de Comparação

Às vezes, alguns dos membros exigem uma lógica de comparação personalizada. Para substituir a regra de comparação, precisamos criar um comparador de valor personalizado ou fornecer uma função para comparar objetos e converter esses objetos em string (opcional) e função de filtro (opcional). Value Comparer deve ser herdado de AbstractValueComparer ou deve implementar IValueComparer .

 public class MyValueComparer: AbstractValueComparer <string> 
{
public override bool Compare (string obj1, string obj2, configurações de ComparisonSettings)
{
return obj1 == obj2; // Implemente lógica de comparação aqui
}
}

Substituir regra de comparação para objetos de tipo específico.

 // Use MyComparer para comparar todos os membros do tipo string 
comparer.AddComparerOverride <string> (new MyValueComparer ());
comparer.AddComparerOverride (typeof (string), new MyValueComparer ());
// Use MyComparer para comparar todos os membros do tipo string exceto membros cujo nome começa com "Xyz"
comparer.AddComparerOverride (typeof (string), new MyValueComparer (), membro =>! member.Name.StartsWith ("Xyz"));
comparer.AddComparerOverride <string> (new MyValueComparer (), membro =>! member.Name.StartsWith ("Xyz"));

Substituir regra de comparação para um determinado membro (campo ou propriedade). Se o parâmetro toStringFunction não for fornecido, os objetos serão convertidos em string usando o método ToString() .

 // Use MyValueComparer para comparar StringProperty of ClassA 
comparer.AddComparerOverride (() => new ClassA (). StringProperty, novo MyValueComparer ());
comparer.AddComparerOverride (
typeof (ClassA) .GetTypeInfo (). GetMember ("StringProperty"). Primeiro (),
new MyValueComparer ());
// Compare StringProperty de ClassA por comprimento. Se length equal considerar que os valores são iguais
comparer.AddComparerOverride (
() => new ClassA (). StringProperty,
(s1, s2, parentSettings) => s1? .Comprimento == s2? .Comprimento,
s => s? .ToString ());
comparer.AddComparerOverride (
() => new ClassA (). StringProperty,
(s1, s2, parentSettings) => s1? .Length == s2? .Length);

Substituir regra de comparação para membro (s) específico (campo ou propriedade) pelo nome.

 // Use MyValueComparer para comparar todos os membros com o nome igual a "StringProperty" 
comparer.AddComparerOverride ("StringProperty", new MyValueComparer ());

As substituições por tipo têm a prioridade mais alta, as substituições por membro e as substituições por nome de membro têm prioridade mais baixa. Se mais de um dos comparadores de valor do mesmo tipo (por tipo / por nome / por nome de membro) puder ser aplicado ao mesmo membro, a exceção AmbiguousComparerOverrideResolutionException será lançada durante a comparação.

Exemplo:

 var a1 = new ClassA (); 
var a2 = new ClassA ();
comparer.AddComparerOverride <string> (valueComparer1, member => member.Name.StartsWith ("String"));
comparer.AddComparerOverride <string> (valueComparer2, member => member.Name.EndsWith ("Propriedade"));
var result = comparer.Compare (a1, a2); // Exceção aqui

Configurações de comparação

O construtor comparador possui um parâmetro de configurações opcional para configurar alguns aspectos da comparação.

Comparação Recursiva

Verdadeiro por padrão. Se true, todos os membros que não são tipos primitivos, não possuem uma regra de comparação personalizada e não implementam ICompareble , serão comparados usando as mesmas regras que os objetos raiz.

EmptyAndNullEnumerablesEqual

Falso por padrão. Se for verdade, enumeráveis vazios (matrizes, coleções, listas, etc.) e valores nulos serão considerados como valores iguais.

UseDefaultIfMemberNotExist

Se true e member não existir, o Object Comparer considerará que esse membro é igual ao valor padrão do tipo de membro oposto. Aplicável somente para comparação de tipos dinâmicos. Falso por padrão.

A classe de configurações de comparação permite armazenar valores personalizados que podem ser usados em comparadores personalizados.

 SetCustomSetting <T> (valor T, chave de cadeia = nulo) 
GetCustomSetting <T> (chave de cadeia = nulo)

Fábrica

Fábrica fornece uma maneira de encapsular a criação e a criação de comparadores. Fábrica deve implementar o IComparersFactory ou deve ser herdado do ComparersFactory .

 public class MyComparersFactory: ComparersFactory 
{
public override IComparer <T> GetObjectsComparer <T> (Configurações de ComparisonSettings = null, IBaseComparer parentComparer = null)
{
if (typeof (T) == typeof (ClassA))
{
var comparer = new Comparador <ClassA> (settings, parentComparer, this);
comparer.AddComparerOverride <Guid> (new MyCustomGuidComparer ());
return (IComparer <T>) comparer;
}
return base.GetObjectsComparer <T> (configurações, parentComparer);
}
}

Comparador não genérico

Este comparador cria uma implementação genérica do comparador para cada comparação.

 var comparer = new Comparer (); 
var isEqual = comparer.Compare (a1, a2);

Comparadores de valor útil

O Framework contém vários comparadores personalizados que podem ser úteis em muitos casos.

DoNotCompareValueComparer

Permite pular alguns campos / tipos. Tem implementação de singleton ( DoNotCompareValueComparer.Instance ).

DynamicValueComparer

Recebe a regra de comparação como uma função.

NulableStringsValueComparer

Seqüências nulas e vazias são consideradas como valores iguais. Tem implementação de singleton ( NulableStringsValueComparer.Instance ).

DefaultValueValueComparer

Permite considerar o valor fornecido e o valor padrão do tipo especificado como valores iguais (consulte o Exemplo 3 abaixo).

IgnoreCaseStringsValueComparer

Permite comparar caso de ignorar cadeia. Tem implementação de singleton ( IgnoreCaseStringsValueComparer.Instance ).

UriComparer

Permite comparar objetos Uri.

Exemplos

Existem alguns exemplos mais complexos de como o Object Comparer pode ser usado.

Exemplo 1: mensagem esperada

Desafio

Verifique se a mensagem recebida é igual à mensagem esperada.

Problemas

  • DateCreated , DateSent e DateReceived precisam ser ignoradas
  • A propriedade Id gerada Id precisa ser ignorada
  • Message propriedade de Message da classe Error precisa ser ignorada

Solução

 public class Error 
{
public int Id {get; conjunto; }
string pública Messgae {get; conjunto; }
}
public class Mensagem
{
public string Id {get; conjunto; }
public DateTime DateCreated {get; conjunto; } public DateTime DateSent {get; conjunto; } public DateTime DateReceived {get; conjunto; } Int público MessageType {get; conjunto; } public int Status {get; conjunto; } public List <Error> Erros {get; conjunto; } string de substituição pública ToString ()
{
return $ "Id: {Id}, Tipo: {MessageType}, Status: {Status}";
}
}

Configurando comparador.

 _comparador = novo Comparador <Mensagem> ( 
new ComparisonSettings
{
// As listas de erros nulas e vazias são iguais
EmptyAndNullEnumerablesEqual = true
});
// Não compare datas
_comparer.AddComparerOverride <DateTime> (DoNotCompareValueComparer.Instance);
// Não compare o ID
_comparer.AddComparerOverride (() => novo Message (). Id, DoNotCompareValueComparer.Instance);
// Não compare o texto da mensagem
_comparer.AddComparerOverride (() => new Erro (). Messgae, DoNotCompareValueComparer.Instance);
var expectedMessage = nova mensagem
{
MessageType = 1,
Status = 0
};
var actualMessage = nova mensagem
{
Id = "M12345",
DateCreated = DateTime.Now,
DateSent = DateTime.Now,
DateReceived = DateTime.Now,
MessageType = 1,
Status = 0
};
Diferenças IEnumerable <Difference>;
var isEqual = _comparer.Compare (expectedMessage, actualMessage, out differences);

Objects are equal

 var expectedMessage = nova mensagem 
{
MessageType = 1,
Status = 1
Erros = nova lista <erro>
{
novo erro {Id = 2},
novo erro {Id = 7}
}
};
var actualMessage = nova mensagem
{
Id = "M12345",
DateCreated = DateTime.Now,
DateSent = DateTime.Now,
DateReceived = DateTime.Now,
MessageType = 1,
Status = 1
Erros = nova lista <erro>
{
novo erro {Id = 2, Messgae = "Algum erro # 2"},
novo erro {Id = 7, Messgae = "Algum erro # 7"},
}
};
Diferenças IEnumerable <Difference>;
var isEqual = _comparer.Compare (expectedMessage, actualMessage, out differences);

Objects are equal

 var expectedMessage = nova mensagem 
{
MessageType = 1,
Status = 1
Erros = nova lista <erro>
{
novo erro {Id = 2, Messgae = "Algum erro # 2"},
novo erro {Id = 8, Messgae = "Algum erro # 8"}
}
};
var actualMessage = nova mensagem
{
Id = "M12345",
DateCreated = DateTime.Now,
DateSent = DateTime.Now,
DateReceived = DateTime.Now,
MessageType = 1,
Status = 2,
Erros = nova lista <erro>
{
novo erro {Id = 2, Messgae = "Algum erro # 2"},
novo erro {Id = 7, Messgae = "Algum erro # 7"}
}
};
Diferenças IEnumerable <Difference>;
var isEqual = _comparer.Compare (expectedMessage, actualMessage, out differences);

Difference: DifferenceType=ValueMismatch, MemberPath='Status', Value1='1', Value2='2'.

Difference: DifferenceType=ValueMismatch, MemberPath='Errors[1].Id', Value1='8', Value2='7'.

Exemplo 2: comparação de pessoas

Desafio

Compare pessoas de diferentes fontes.

Problemas

  • Formato PhoneNumber pode estar em diferente. Exemplo: "111-555-8888" e "(111) 555 8888"
  • MiddleName pode existir em uma fonte, mas não existe em outra fonte. Faz sentido comparar MiddleName somente se tiver valor nas duas origens.
  • Propriedade PersonId precisa ser ignorada

Solução

 pessoa de classe pública 
{
public Guid PersonId {get; conjunto; }
public string FirstName {get; conjunto; } public string LastName {get; conjunto; } cadeia pública MiddleName {get; conjunto; } string pública PhoneNumber {get; conjunto; } string de substituição pública ToString ()
{
return $ "{FirstName} {MiddleName} {LastName} ({PhoneNumber})";
}
}

Número de telefone pode ter diferentes formatos. Vamos comparar apenas dígitos.

 classe pública PhoneNumberComparer: AbstractValueComparer <string> 
{
public override bool Compare (string obj1, string obj2, configurações de ComparisonSettings)
{
return ExtractDigits (obj1) == ExtractDigits (obj2);
}
private string ExtractDigits (string str)
{
return string.Junte (
string.Empty,
(str ?? string.Empty)
.ToCharArray ()
.Onde (char.IsDigit));
}
}

Fábrica permite não configurar o comparador toda vez que precisarmos criá-lo.

 public class MyComparersFactory: ComparersFactory 
{
public override IComparer <T> GetObjectsComparer <T> (Configurações de ComparisonSettings = null, IBaseComparer parentComparer = null)
{
if (typeof (T) == typeof (Person))
{
var comparer = new Comparer <Pessoa> (settings, parentComparer, this);
// Não compare PersonId
comparer.AddComparerOverride <Guid> (DoNotCompareValueComparer.Instance);
// Às vezes o MiddleName pode ser ignorado. Compare apenas se a propriedade tiver valor.
comparer.AddComparerOverride (
() => new Person (). MiddleName,
(s1, s2, parentSettings) => string.IsNullOrWhiteSpace (s1) || string.IsNullOrWhiteSpace (s2) || s1 == s2);
comparer.AddComparerOverride (
() => nova pessoa (). PhoneNumber,
novo PhoneNumberComparer ());
return (IComparer <T>) comparer;
}
return base.GetObjectsComparer <T> (configurações, parentComparer);
}
}

Configurando comparador.

 _factory = new MyComparersFactory (); 
_comparador = _factory.GetObjectsComparer <Person> ();
var person1 = nova pessoa
{
PersonId = Guid.NewGuid (),
FirstName = "John",
LastName = "Doe",
MiddleName = "F",
PhoneNumber = "111-555-8888"
};
var person2 = nova pessoa
{
PersonId = Guid.NewGuid (),
FirstName = "John",
LastName = "Doe",
PhoneNumber = "(111) 555 8888"
};
Diferenças IEnumerable <Difference>;
var isEqual = _comparador.Compare (person1, person2, out differences);

Objetos são iguais

 var person1 = nova pessoa 
{
PersonId = Guid.NewGuid (),
FirstName = "Jack",
LastName = "Doe",
MiddleName = "F",
PhoneNumber = "111-555-8888"
};
var person2 = nova pessoa
{
PersonId = Guid.NewGuid (),
FirstName = "John",
LastName = "Doe",
MiddleName = "L",
PhoneNumber = "222-555-9999"
};
Diferenças IEnumerable <Difference>;
var isEqual = _comparador.Compare (person1, person2, out differences);

Diferença: DifferenceType = ValueMismatch, MemberPath = 'FirstName', Value1 = 'Jack', Value2 = 'John'.

Diferença: DifferenceType = ValueMismatch, MemberPath = 'MiddleName', Value1 = 'F', Value2 = 'L'.

Diferença: DifferenceType = ValueMismatch, MemberPath = 'PhoneNumber', Value1 = '111–555–8888', Value2 = '222–555–9999'.

Exemplo 3: Comparando Arquivos de Configuração JSON

Desafio

Existem arquivos com configurações com algumas diferenças que precisam ser encontradas. O Json.NET é usado para desserializar dados JSON.

Problemas

  • URLs podem ser com ou sem prefixo http.
  • DataCompression está desativado por padrão
  • SmartMode1… 3 desativado por padrão
  • ConnectionString , Email e Notificações precisam ser ignorados
  • Se as configurações ProcessTaskTimeout ou TotalProcessTimeout forem ignoradas, os valores padrão serão usados, portanto, se uma configuração de arquivo não existir e em outro arquivo essa configuração tiver valor padrão, ela será realmente a mesma.

arquivos

Configurações0

 { 
"ConnectionString": "USER ID = superusuário; PASSWORD = superpassword; DATA SOURCE = localhost: 1111",
"O email": {
"Porto": 25,
"Host": "MyHost.com",
"EmailAddress": "test@MyHost.com"
}
"Definições": {
"DataCompression": "On",
"DataSourceType": "MultiDataSource",
"SomeUrl": "http://MyHost.com/VeryImportantData",
"SomeOtherUrl": "http://MyHost.com/NotSoImportantData/",
"CacheMode": "Memória",
"MaxCacheSize": "1GB",
"SuperModes": {
"SmartMode1": "Ativado",
"SmartMode2": "Desativado",
"SmartMode3": "Ativado"
}
}
"Tempos limite": {
"TotalProcessTimeout": 500,
"ProcessTaskTimeout": 100
}
"BackupSettings": {
"BackupIntervalUnit": "Dia",
"BackupInterval": 100
}
"Notificações": [
{
"Telefone": "111-222-3333"
}
{
"Telefone": "111-222-4444"
}
{
"EMail": "support@MyHost.com"
}
]
"Exploração madeireira": {
"Ativado": true
"Padrão": "Registros \ MyApplication.% Data {yyyyMMdd} .log",
"MaximumFileSize": "20MB",
"Level": "ALL"
}
}

Configurações1

 { 
"ConnectionString": "ID DO USUÁRIO = admin; PASSWORD = *****; FONTE DE DADOS = localhost: 22222",
"O email": {
"Porto": 25,
"Host": "MyHost.com",
"EmailAddress": "test@MyHost.com"
}
"Definições": {
"DataCompression": "On",
"DataSourceType": "MultiDataSource",
"SomeUrl": "MyHost.com/VeryImportantData",
"SomeOtherUrl": "MyHost.com/NotSoImportantData/",
"CacheMode": "Memória",
"MaxCacheSize": "1GB",
"SuperModes": {
"SmartMode1": "ativado",
"SmartMode3": "ativado"
}
}
"BackupSettings": {
"BackupIntervalUnit": "Dia",
"BackupInterval": 100
}
"Notificações": [
{
"Telefone": "111-222-3333"
}
{
"EMail": "support@MyHost.com"
}
]
"Exploração madeireira": {
"Ativado": true
"Padrão": "Registros \ MyApplication.% Data {yyyyMMdd} .log",
"MaximumFileSize": "20MB",
"Level": "ALL"
}
}

Configurações2

 var settings0Json = LoadJson ("Settings0.json"); 
var settings0 = JsonConvert.DeserializeObject <ExpandoObject> (settings0Json);
var settings2Json = LoadJson ("Settings2.json");
var settings2 = JsonConvert.DeserializeObject <ExpandoObject> (settings2Json);
Diferenças IEnumerable <Difference>;
var isEqual = _comparador.Compare (settings0, settings2, out differences);

Difference: DifferenceType=ValueMismatch, MemberPath='Settings.DataCompression', Value1='On', Value2='Off'.

Difference: DifferenceType=ValueMismatch, MemberPath='Settings.SuperModes.SmartMode1', Value1='Enabled', Value2='Disabled'.

Difference: DifferenceType=ValueMismatch, MemberPath='Timeouts.ProcessTaskTimeout', Value1='100', Value2='200'.

Difference: DifferenceType=ValueMismatch, MemberPath='BackupSettings.BackupIntervalUnit', Value1='Day', Value2='Week'.

Difference: DifferenceType=ValueMismatch, MemberPath='BackupSettings.BackupInterval', Value1='100', Value2='2'.

Difference: DifferenceType=ValueMismatch, MemberPath='Logging.Enabled', Value1='True', Value2='False'.

Difference: DifferenceType=ValueMismatch, MemberPath='Logging.MaximumFileSize', Value1='20MB', Value2='40MB'.

Difference: DifferenceType=ValueMismatch, MemberPath='Logging.Level', Value1='ALL', Value2='ERROR'.

É isso aí. Apreciar.