União Vs Concat no Linq

86

Tenho uma pergunta sobre Unione Concat. Eu acho que ambos estão se comportando da mesma forma no caso de List<T>.

var a1 = (new[] { 1, 2 }).Union(new[] { 1, 2 });             // O/P : 1 2
var a2 = (new[] { 1, 2 }).Concat(new[] { 1, 2 });            // O/P : 1 2 1 2

var a3 = (new[] { "1", "2" }).Union(new[] { "1", "2" });     // O/P : "1" "2"
var a4 = (new[] { "1", "2" }).Concat(new[] { "1", "2" });    // O/P : "1" "2" "1" "2"

Os resultados acima são esperados,

Mas no caso de List<T>estou obtendo o mesmo resultado.

class X
{
    public int ID { get; set; }
}

class X1 : X
{
    public int ID1 { get; set; }
}

class X2 : X
{
    public int ID2 { get; set; }
}

var lstX1 = new List<X1> { new X1 { ID = 10, ID1 = 10 }, new X1 { ID = 10, ID1 = 10 } };
var lstX2 = new List<X2> { new X2 { ID = 10, ID2 = 10 }, new X2 { ID = 10, ID2 = 10 } };

var a5 = lstX1.Cast<X>().Union(lstX2.Cast<X>());     // O/P : a5.Count() = 4
var a6 = lstX1.Cast<X>().Concat(lstX2.Cast<X>());    // O/P : a6.Count() = 4

Mas ambos estão se comportando da mesma maneira List<T>.

Alguma sugestão, por favor?

Prasad Kanaparthi
fonte
1
Se você sabe a diferença entre esses dois métodos, por que o resultado o surpreende? É uma consequência direta da funcionalidade dos métodos.
Konrad Rudolph
@KonradRudolph, O que quero dizer é no caso de List <T>, posso usar qualquer um 'Union' / 'Concat'. Porque ambos estão se comportando da mesma maneira.
Prasad Kanaparthi
Não, obviamente não. Eles não se comportam da mesma forma, como mostra seu primeiro exemplo.
Konrad Rudolph
Em seu exemplo, todos os IDs são diferentes.
Jim Mischel
@JimMischel, Editou minha postagem. mesmo com os mesmos valores também está se comportando da mesma forma.
Prasad Kanaparthi

Respostas:

110

Union retorna Distinctvalores. Por padrão, ele irá comparar referências de itens. Seus itens possuem referências diferentes, portanto, todos são considerados diferentes. Quando você lança para o tipo de base X, a referência não é alterada.

Se você substituir Equalse GetHashCode(usado para selecionar itens distintos), os itens não serão comparados por referência:

class X
{
    public int ID { get; set; }

    public override bool Equals(object obj)
    {
        X x = obj as X;
        if (x == null)
            return false;
        return x.ID == ID;
    }

    public override int GetHashCode()
    {
        return ID.GetHashCode();
    }
}

Mas todos os seus itens têm valores diferentes de ID. Portanto, todos os itens ainda são considerados diferentes. Se você fornecer vários itens com o mesmo ID, verá a diferença entre Unione Concat:

var lstX1 = new List<X1> { new X1 { ID = 1, ID1 = 10 }, 
                           new X1 { ID = 10, ID1 = 100 } };
var lstX2 = new List<X2> { new X2 { ID = 1, ID2 = 20 }, // ID changed here
                           new X2 { ID = 20, ID2 = 200 } };

var a5 = lstX1.Cast<X>().Union(lstX2.Cast<X>());  // 3 distinct items
var a6 = lstX1.Cast<X>().Concat(lstX2.Cast<X>()); // 4

Seu exemplo inicial funciona, porque inteiros são tipos de valor e são comparados por valor.

Sergey Berezovskiy
fonte
3
Mesmo se não estivesse comparando referências, mas, por exemplo, os IDs dentro, ainda haveria quatro itens, pois os IDs são diferentes.
Rawling
@Swani não, eles não são. Acho que você não alterou a ID do primeiro item na segunda coleção, como afirmei acima
Sergey Berezovskiy
@Swani então você não substituiu Equals e GetHashCode, como afirmei acima
Sergey Berezovskiy
@lazyberezovsky, concordo com sua resposta. Mas ainda não estou feliz com os comentários. Se você executar meu código de amostra, poderá ver o mesmo resultado para 'a5' e 'a6'. Não procuro solução. Mas por que 'Concat' e 'Union' se comportam da mesma forma naquela sessão. Por favor, responda.
Prasad Kanaparthi
3
@Swani desculpe, foi afk. x.Union(y)é o mesmo que x.Concat(y).Distinct(). Portanto, a diferença é apenas com a aplicação Distinct. Como o Linq seleciona objetos distintos (ou seja, diferentes) em sequências concatenadas? Em seu código de amostra (da pergunta), o Linq compara objetos por referência (ou seja, endereço na memória). Quando você cria um novo objeto por meio do newoperador, ele aloca memória no novo endereço. Portanto, quando você tiver quatro novos objetos criados, os endereços serão diferentes. E todos os objetos serão distintos. Assim Distinct, retornará todos os objetos da sequência.
Sergey Berezovskiy de
48

Concatretorna literalmente os itens da primeira sequência seguidos pelos itens da segunda sequência. Se você usar Concatem duas sequências de 2 itens, sempre obterá uma sequência de 4 itens.

Union é essencialmente Concat seguido por Distinct.

Em seus dois primeiros casos, você termina com sequências de 2 itens porque, entre eles, cada par de sequências de entrada tem exatamente dois itens distintos.

Em seu terceiro caso, você termina com uma sequência de 4 itens porque todos os quatro itens em suas duas sequências de entrada são distintos .

Rawling
fonte
14

Unione Concatse comportar da mesma forma, pois Unionnão pode detectar duplicatas sem um personalizado IEqualityComparer<X>. É só ver se ambos são a mesma referência.

public class XComparer: IEqualityComparer<X>
{
    public bool Equals(X x1, X x2)
    {
        if (object.ReferenceEquals(x1, x2))
            return true;
        if (x1 == null || x2 == null)
            return false;
        return x1.ID.Equals(x2.ID);
    }

    public int GetHashCode(X x)
    {
        return x.ID.GetHashCode();
    }
}

Agora você pode usá-lo na sobrecarga de Union:

var comparer = new XComparer();
a5 = lstX1.Cast<X>().Union(lstX2.Cast<X>(), new XComparer()); 
Tim Schmelter
fonte