Union Vs Concat w Linq

86

Mam pytanie dotyczące Unioni Concat. Myślę, że oba zachowują się tak samo w przypadku 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"

Powyższy wynik jest oczekiwany,

Ale Incase of List<T>ja otrzymuję ten sam wynik.

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

Ale obaj zachowują się w ten sam sposób List<T>.

Jakieś sugestie?

Prasad Kanaparthi
źródło
1
Jeśli znasz różnicę między tymi dwiema metodami, dlaczego wynik cię zaskakuje? Jest to bezpośrednia konsekwencja funkcjonalności metod.
Konrad Rudolph
@KonradRudolph, Mam na myśli to, że w przypadku List <T> mogę użyć dowolnego „Union” / „Concat”. Ponieważ oboje zachowują się tak samo.
Prasad Kanaparthi
Nie, oczywiście, że nie. Nie zachowują się tak samo, jak pokazuje twój pierwszy przykład.
Konrad Rudolph
W twoim przykładzie wszystkie identyfikatory są różne.
Jim Mischel
@JimMischel, Edytowałem mój post. nawet przy takich samych wartościach zachowuje się tak samo.
Prasad Kanaparthi

Odpowiedzi:

110

Unia zwraca Distinctwartości. Domyślnie porówna referencje pozycji. Twoje przedmioty mają różne odniesienia, dlatego wszystkie są traktowane jako różne. Podczas rzutowania na typ podstawowy Xodniesienie nie jest zmieniane.

Jeśli zastąpisz Equalsi GetHashCode(służy do wybierania odrębnych elementów), elementy nie będą porównywane według odniesienia:

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();
    }
}

Ale wszystkie Twoje przedmioty mają inną wartość ID. Więc wszystkie przedmioty nadal są uważane za różne. Jeśli dostarczysz kilka przedmiotów z tym samym ID, zobaczysz różnicę między Uniona 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

Twój początkowy przykład działa, ponieważ liczby całkowite są typami wartości i są porównywane według wartości.

Sergey Berezovskiy
źródło
3
Nawet gdyby nie porównywał referencji, ale np. Identyfikatorów wewnątrz, nadal byłyby cztery elementy, ponieważ identyfikatory są różne.
Rawling
@Swani nie, nie są. Myślę, że nie zmieniłeś identyfikatora pierwszej pozycji w drugiej kolekcji, jak powiedziałem powyżej
Sergey Berezovskiy
@Swani to nie zastąpiłeś Equals i GetHashCode, jak powiedziałem powyżej
Sergey Berezovskiy
@lazyberezovsky, zgadzam się z twoją odpowiedzią. Ale nadal nie jestem zadowolony z komentarzy. Jeśli wykonasz mój przykładowy kod, możesz zobaczyć ten sam wynik dla „a5” i „a6”. Nie szukam rozwiązania. Ale dlaczego „Concat” i „Union” zachowują się tak samo w tej sytuacji. Proszę odpowiedz.
Prasad Kanaparthi
3
@Swani przepraszam, był afk. x.Union(y)jest taki sam jak x.Concat(y).Distinct(). Różnica polega więc tylko na stosowaniu Distinct. W jaki sposób Linq wybiera odrębne (tj. Różne) obiekty w połączonych sekwencjach? W twoim przykładowym kodzie (z pytania) Linq porównuje obiekty przez odniesienie (tj. Adres w pamięci). Kiedy tworzysz nowy obiekt za pomocą newoperatora, przydziela pamięć pod nowym adresem. Tak więc, gdy masz cztery nowo utworzone obiekty, adresy będą różne. I wszystkie przedmioty będą różne. W ten sposób Distinctzwróci wszystkie obiekty z sekwencji.
Sergey Berezovskiy
48

Concatdosłownie zwraca elementy z pierwszej sekwencji, po których następują elementy z drugiej sekwencji. Jeśli użyjesz Concatna dwóch sekwencjach 2-elementowych, zawsze otrzymasz sekwencję 4-elementową.

Unionzasadniczo Concatnastępuje Distinct.

W pierwszych dwóch przypadkach otrzymujesz sekwencje 2-elementowe, ponieważ między nimi każda para kwadratów wejściowych ma dokładnie dwa różne elementy.

W trzecim przypadku otrzymasz sekwencję 4 elementów, ponieważ wszystkie cztery elementy w twoich dwóch sekwencjach wejściowych są różne .

Rawling
źródło
14

Unioni Concatzachowują się tak samo, ponieważ Unionnie można wykryć duplikatów bez niestandardowego IEqualityComparer<X>. Po prostu patrzy, czy oba są tym samym odniesieniem.

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();
    }
}

Teraz możesz go użyć w przeciążeniu Union:

var comparer = new XComparer();
a5 = lstX1.Cast<X>().Union(lstX2.Cast<X>(), new XComparer()); 
Tim Schmelter
źródło