Introdução
Eliminar duplicidade de uma coleção de dados nunca foi uma tarefa fácil, cada desenvolvedor resolvia isso de uma forma implementando suas próprias soluções , com o Linq essa tarefa fico muito mais fácil.
Nesse artigo vamos analisar como utilizar o método Distinct, assim como o Distinct do SQL o método Distinct do Linq tem o mesmo objetivo eliminar as duplicidade, vamos analisar também a implementação da interface IEqualityComparer(T).
Recursos utilizados
- Visual Studio 2008 Express Edition SP1
Criando projeto
Para demonstra com utilizar o método Distinct vamos criar um DataTable e uma coleção de objetos com dados duplicados.
Crie uma aplicação do Tipo Console Application.
Conforme código abaixo, adicione o método GetDataTable que cria e carrega um DataTable na definição da classe Program.
static DataTable GetDataTable()
{
//Define DataTable
DataTable table = new DataTable("Classificacao");
table.Columns.Add("NomeTime", typeof(string));
table.Columns.Add("Posicao", typeof(int));
//Adiciona linhas
DataRow row;
row = table.NewRow();
row["NomeTime"] = "Corinthians";
row["Posicao"] = "1";
table.Rows.Add(row);
row = table.NewRow();
row["NomeTime"] = "Santos";
row["Posicao"] = "2";
table.Rows.Add(row);
row = table.NewRow();
row["NomeTime"] = "São Paulo";
row["Posicao"] = "3";
table.Rows.Add(row);
row = table.NewRow();
row["NomeTime"] = "Palmeiras";
row["Posicao"] = "4";
table.Rows.Add(row);
row = table.NewRow();
row["NomeTime"] = "Flamengo";
row["Posicao"] = "5";
table.Rows.Add(row);
//Duplica duas linhas
row = table.NewRow();
row["NomeTime"] = "Corinthians";
row["Posicao"] = "1";
table.Rows.Add(row);
row = table.NewRow();
row["NomeTime"] = "Santos";
row["Posicao"] = "2";
table.Rows.Add(row);
return table;
}
Function GetDataTable() As DataTable
'Define DataTable
Dim table As New DataTable("Classificacao")
table.Columns.Add("NomeTime", GetType(String))
table.Columns.Add("Posicao", GetType(Int32))
'Adiciona linhas
Dim row As DataRow
row = table.NewRow()
row("NomeTime") = "Corinthians"
row("Posicao") = "1"
table.Rows.Add(row)
row = table.NewRow()
row("NomeTime") = "Santos"
row("Posicao") = "2"
table.Rows.Add(row)
row = table.NewRow()
row("NomeTime") = "São Paulo"
row("Posicao") = "3"
table.Rows.Add(row)
row = table.NewRow()
row("NomeTime") = "Palmeiras"
row("Posicao") = "4"
table.Rows.Add(row)
row = table.NewRow()
row("NomeTime") = "Flamengo"
row("Posicao") = "5"
table.Rows.Add(row)
'Duplica duas linhas
row = table.NewRow()
row("NomeTime") = "Corinthians"
row("Posicao") = "1"
table.Rows.Add(row)
row = table.NewRow()
row("NomeTime") = "Santos"
row("Posicao") = "2"
table.Rows.Add(row)
Return table
End Function
Conforme código abaixo, adicione uma classe ao projeto, altere o nome para Classificacao e na definição da classe adicione duas propriedade , NomeTime e Posicao.
class Classificacao
{
public string NomeTime { get; set; }
public int Posicao { get; set; }
}
Class Classificacao
Private _NomeTime As String
Public Property NomeTime() As String
Get
Return _NomeTime
End Get
Set(ByVal value As String)
_NomeTime = value
End Set
End Property
Private _Posicao As Int32
Public Property Posicao() As Int32
Get
Return _Posicao
End Get
Set(ByVal value As Int32)
_Posicao = value
End Set
End Property
End Class
Conforme código abaixo,adicione o método getClassificacao que cria e carrega uma coleção de objetos Classificacao na definição da classe Program.
static Classificacao[] getClassificacao()
{
Classificacao[] classificacao = {
new Classificacao { NomeTime = "Corinthians", Posicao = 1 },
new Classificacao { NomeTime = "Santos", Posicao = 2 },
new Classificacao { NomeTime = "São Paulo", Posicao = 3 },
new Classificacao { NomeTime = "Palmeiras", Posicao = 4 },
new Classificacao { NomeTime = "Flamengo", Posicao = 5 },
new Classificacao { NomeTime = "Corinthians", Posicao = 1 },
new Classificacao { NomeTime = "Santos", Posicao = 2 }};
return classificacao;
}
Function getClassificacao() As Classificacao()
Dim objClassificacao As Classificacao() = { _
New Classificacao With {.NomeTime = "Corinthians", .Posicao = 1}, _
New Classificacao With {.NomeTime = "Santos", .Posicao = 2}, _
New Classificacao With {.NomeTime = "São Paulo", .Posicao = 3}, _
New Classificacao With {.NomeTime = "Palmeiras", .Posicao = 4}, _
New Classificacao With {.NomeTime = "Flamengo", .Posicao = 5}, _
New Classificacao With {.NomeTime = "Corinthians", .Posicao = 1}, _
New Classificacao With {.NomeTime = "Santos", .Posicao = 2}}
Return objClassificacao
End Function
Entendendo o método Distinct
O método Distinct retorna uma interface IEnumerable(T) e possui duas sobrecargas, a primeira sobrecarga não recebe nenhum parâmetro e elimina duplicidade somente de objeto de uma mesma instância, no código abaixo temos uma coleção de objetos Classificacao com quatro itens de uma única instância, para eliminar os objetos duplicados nessa situação podemos utilizar o método Distinct sem passar a interface IEqualityComparer(T).
Classificacao classificacao = new Classificacao();
classificacao.NomeTime = "Corinthians";
classificacao.Posicao = 1;
//Cria coleção com quatro instância do mesmo objeto
Classificacao[] obj = { classificacao, classificacao, classificacao, classificacao };
foreach (Classificacao item in obj.Distinct())
{
Console.WriteLine("{0,-20}{1,-20}", item.NomeTime, item.Posicao);
}
Dim objClassificacao As New Classificacao()
objClassificacao.NomeTime = "Corinthians"
objClassificacao.Posicao = 1
Dim obj As Classificacao() = {objClassificacao, objClassificacao, objClassificacao, objClassificacao}
For Each item As Classificacao In obj.Distinct()
Console.WriteLine("{0,-20}{1,-20}", item.NomeTime, item.Posicao)
Next
Para eliminar objetos de instância diferente mais com valores duplicados, precisamos implementar a interface IEqualityComparer(T), e definir qual será a lógica utilizada e passar a interface como parâmetro para o método Distinct.
Utilizando Distinct em um DataTable
Para utilizar o Distinct em uma coleção de DataRow a Framework disponibiliza uma implementação da interface IEqualityComparer(T) , facilitando a utilização do método,essa implementação esta na classe DataRowComparer essa classe é do tipo static e possui uma propriedade chamada Default que retorna uma instância da interface com a implementação necessária para eliminar objetos DataRow duplicados.
Conforme código abaixo, adicione o método SelectDistinctDataTable na definição da classe Program e chame-o do método Main.
static void SelectDistinctDataTable()
{
DataTable table = GetDataTable();
Console.WriteLine("*** Todas as linhas do DataTable ***\r\n");
foreach (DataRow dataRow in table.Rows)
{
Console.WriteLine("{0,-20}{1,-20}",dataRow.Field<string>(0),dataRow.Field<int>(1));
}
//Elemina todas os objetos DataRow duplicados.
IEnumerable<DataRow> distinct = table.AsEnumerable().Distinct(DataRowComparer.Default);
Console.WriteLine("\r\n*** Somente as linhas sem duplicidade ***\r\n");
foreach (DataRow dataRow in distinct)
{
Console.WriteLine("{0,-20}{1,-20}", dataRow.Field<string>(0), dataRow.Field<int>(1));
}
}
Sub SelectDistinctDataTable()
Dim table As DataTable = GetDataTable()
Console.WriteLine("*** Todas as linhas do DataTable ***")
For Each dataRow As DataRow In table.Rows
Console.WriteLine("{0,-20}{1,-20}", dataRow.Field(Of String)(0), dataRow.Field(Of Int32)(1))
Next
'Elemina todas os objetos DataRow duplicados.
Dim distinct As IEnumerable(Of DataRow) = table.AsEnumerable().Distinct(DataRowComparer.Default)
Console.WriteLine("*** Somente as linhas sem duplicidade ***")
For Each dataRow As DataRow In distinct
Console.WriteLine("{0,-20}{1,-20}", dataRow.Field(Of String)(0), dataRow.Field(Of Int32)(1))
Next
End Sub
Como o método Distinct utiliza a interface IEqualityComparer(T)
A Interface IEqualityComparer(T) define dois métodos :
bool Equals(T x, T y);
int GetHashCode(T obj);
A Interface IEqualityComparer(T) e analisada da seguinte forma quando utilizada pelo método Distinct.
O método GetHashCode e o primeiro método analisado se o valor retornado for único, não será necessário analisar o método Equals, caso o valor de retorno do método GetHashCode não seja único o método Equals será analisado, o retorno do método Equals definir se os objetos passados como parâmetros são iguais (True) ou não (False).
Implementando IEqualityComparer(T)
Vamos utilizar o método Distinct em uma coleção de objetos Classificacao, para fazer isso precisamos implementar a interface IEqualityComparer(T).
Conforme código abaixo, adicione uma classe ao projeto, altere o nome para ClassificacaoComparer e implemente a interface IEqualityComparer(T).
class ClassificacaoComparer : IEqualityComparer<Classificacao>
{
private static ClassificacaoComparer classificacaoComparer;
public static ClassificacaoComparer Default
{
get
{
if (classificacaoComparer == null)
classificacaoComparer = new ClassificacaoComparer();
return classificacaoComparer;
}
}
#region IEqualityComparer<Classificacao> Members
public bool Equals(Classificacao x, Classificacao y)
{
return x.NomeTime == y.NomeTime && x.Posicao == y.Posicao;
}
public int GetHashCode(Classificacao obj)
{
return classificacaoComparer.GetHashCode();
}
#endregion
}
Class ClassificacaoComparer
Implements IEqualityComparer(Of Classificacao)
Shared _ClassificacaoComparer As ClassificacaoComparer
Public Shared ReadOnly Property _Default() As ClassificacaoComparer
Get
If _ClassificacaoComparer Is Nothing Then
_ClassificacaoComparer = New ClassificacaoComparer()
End If
Return _ClassificacaoComparer
End Get
End Property
Public Function Equals1(ByVal x As Classificacao, ByVal y As Classificacao) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Classificacao).Equals
Return x.NomeTime = y.NomeTime And x.Posicao = y.Posicao
End Function
Public Function GetHashCode1(ByVal obj As Classificacao) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Classificacao).GetHashCode
Return _ClassificacaoComparer.GetHashCode()
End Function
End Class
Na implementação da classe ClassificacaoComparer note que utilizamos um padrão de projeto muito conhecido o Singleton, e por esse motivo o HashCode do objeto ClassificacaoComparer que terá apenas uma instância será utilizado com retorno para o método GetHashCode.
No método Equals cada propriedade dos objetos passados como parâmetro são comparadas entre si para determinar se os objetos são iguais ou diferentes.
Utilizando Distinct na coleção de objetos Classificacao
Vamos testar a implementação do interface IEqualityComparer(T).
Conforme código abaixo, adicione o método SelectDistinctClassificacao na definição da classe Program e chame-o do método Main.
static void SelectDistinctClassificacao()
{
Classificacao[] classificacao = getClassificacao();
Console.WriteLine("\r\n*** Todas as linhas da coleção ***\r\n");
foreach (Classificacao item in classificacao)
{
Console.WriteLine("{0,-20}{1,-20}", item.NomeTime, item.Posicao);
}
Console.WriteLine("\r\n*** Eliminando duplicidade ***\r\n");
foreach (Classificacao item in classificacao.Distinct(ClassificacaoComparer.Default))
{
Console.WriteLine("{0,-20}{1,-20}", item.NomeTime, item.Posicao);
}
}
Sub SelectDistinctClassificacao()
Dim objClassificacao As Classificacao() = getClassificacao()
Console.WriteLine("*** Todas as linhas da coleção ***")
For Each item As Classificacao In objClassificacao
Console.WriteLine("{0,-20}{1,-20}", item.NomeTime, item.Posicao)
Next
Console.WriteLine("*** Eliminando duplicidade ***")
For Each item As Classificacao In objClassificacao.Distinct(ClassificacaoComparer._Default)
Console.WriteLine("{0,-20}{1,-20}", item.NomeTime, item.Posicao)
Next
End Sub
Conclusão
Eliminar duplicidades em coleções de objetos não e mais um problema, o método Distinct facilita em muito nossa vida, precisamos apenas nos preocupar com a implementação da interface IEqualityComparer(T).
O resultado esperado depende somente da correta implementação da interface, e não precisamos nos preocupar com questões como desempenho, lógica, segurança ou qualquer outro detalhe.
Código Fonte
Daniel Fonseca Castro