t

Comentários recentes

Distinct com Linq

Postado por Daniel Fonseca Castro - Wednesday, July 30, 2008 10:59 AM

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

Currently rated 2.2 by 6 people

  • Currently 2.166667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: Linq

Related posts

Add comment


 

  Country flag