Tenho uma pergunta sobre Union
e 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?
Respostas:
Union retorna
Distinct
valores. 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 baseX
, a referência não é alterada.Se você substituir
Equals
eGetHashCode
(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 mesmoID
, verá a diferença entreUnion
eConcat
: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.
fonte
x.Union(y)
é o mesmo quex.Concat(y).Distinct()
. Portanto, a diferença é apenas com a aplicaçãoDistinct
. 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 donew
operador, 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. AssimDistinct
, retornará todos os objetos da sequência.Concat
retorna literalmente os itens da primeira sequência seguidos pelos itens da segunda sequência. Se você usarConcat
em duas sequências de 2 itens, sempre obterá uma sequência de 4 itens.Union
é essencialmenteConcat
seguido porDistinct
.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 .
fonte
Union
eConcat
se comportar da mesma forma, poisUnion
não pode detectar duplicatas sem um personalizadoIEqualityComparer<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());
fonte