Mam pytanie dotyczące Union
i 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?
Odpowiedzi:
Unia zwraca
Distinct
wartości. Domyślnie porówna referencje pozycji. Twoje przedmioty mają różne odniesienia, dlatego wszystkie są traktowane jako różne. Podczas rzutowania na typ podstawowyX
odniesienie nie jest zmieniane.Jeśli zastąpisz
Equals
iGetHashCode
(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 samymID
, zobaczysz różnicę międzyUnion
aConcat
: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.
źródło
x.Union(y)
jest taki sam jakx.Concat(y).Distinct()
. Różnica polega więc tylko na stosowaniuDistinct
. 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ąnew
operatora, 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óbDistinct
zwróci wszystkie obiekty z sekwencji.Concat
dosłownie zwraca elementy z pierwszej sekwencji, po których następują elementy z drugiej sekwencji. Jeśli użyjeszConcat
na dwóch sekwencjach 2-elementowych, zawsze otrzymasz sekwencję 4-elementową.Union
zasadniczoConcat
następujeDistinct
.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 .
źródło
Union
iConcat
zachowują się tak samo, ponieważUnion
nie można wykryć duplikatów bez niestandardowegoIEqualityComparer<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());
źródło