Union Vs Concat в Linq

86

У меня вопрос по Unionи Concat. Я думаю, оба ведут себя одинаково в случае 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"

Вышеуказанный результат ожидаем,

Но в случае если List<T>я получаю тот же результат.

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

Но оба ведут себя одинаково List<T>.

Есть предложения, пожалуйста?

Прасад Канапарти
источник
1
Если вы знаете разницу между этими двумя методами, почему результат вас удивляет? Это прямое следствие функциональности методов.
Конрад Рудольф
@KonradRudolph, я имею в виду, что в случае List <T> я могу использовать любой один 'Union' / 'Concat'. Потому что оба ведут себя одинаково.
Прасад Канапарти
Нет, конечно, нет. Они не ведут себя так же, как показывает ваш первый пример.
Конрад Рудольф
В вашем примере все идентификаторы разные.
Джим Мишель
@JimMischel, отредактировал мой пост. даже с одинаковыми значениями он ведет себя одинаково.
Прасад Канапарти

Ответы:

110

Union возвращает Distinctзначения. По умолчанию он будет сравнивать ссылки на товары. У ваших товаров разные ссылки, поэтому все они считаются разными. При приведении к базовому типу Xссылка не изменяется.

Если вы переопределите Equalsи GetHashCode(используется для выбора отдельных элементов), то элементы не будут сравниваться по ссылке:

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

Но все ваши предметы имеют разную стоимость ID. Так что все предметы по-прежнему считаются разными. Если вы предоставите несколько одинаковых товаров, IDвы увидите разницу между Unionи 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

Ваш первоначальный образец работает, потому что целые числа являются типами значений и сравниваются по значению.

Сергей Березовский
источник
3
Даже если сравнивать не ссылки, а, например, идентификаторы внутри, все равно будет четыре элемента, поскольку идентификаторы разные.
Rawling
@Swani нет, это не так. Думаю, вы не меняли ID первого предмета во второй коллекции, как я уже говорил выше
Сергей Березовский
@Swani, значит, вы не переопределяете Equals и GetHashCode, как я уже говорил выше
Сергей Березовский,
@lazyberezovsky, согласен с вашим ответом. Но я все еще недоволен комментариями. Если вы выполните мой пример кода, вы увидите тот же результат для «a5» и «a6». Я не ищу решения. Но почему Concat и Union ведут себя одинаково в этой ситуации. Ответьте, пожалуйста.
Прасад Канапарти
3
@Swani, извини, был афк x.Union(y)такое же, как x.Concat(y).Distinct(). Так что разница только в применении Distinct. Как Linq выбирает отдельные (т.е. разные) объекты в сцепленных последовательностях? В вашем примере кода (из вопроса) Linq сравнивает объекты по ссылке (то есть по адресу в памяти). Когда вы создаете новый объект с помощью newоператора, он выделяет память по новому адресу. Итак, когда у вас будет четыре новых созданных объекта, адреса будут другими. И все объекты будут отличными. Таким образом Distinctвернет все объекты из последовательности.
Сергей Березовский
48

Concatбуквально возвращает элементы из первой последовательности, за которыми следуют элементы из второй последовательности. Если вы используете Concatдве последовательности из 2 элементов, вы всегда получите последовательность из 4 элементов.

Unionпо существу Concatследует Distinct.

В ваших первых двух случаях вы получите последовательности из двух элементов, потому что между ними каждая пара входных последовательностей имеет ровно два различных элемента.

В третьем случае вы получите последовательность из 4 элементов, потому что все четыре элемента в ваших двух входных последовательностях различны .

Роулинг
источник
14

Unionи Concatвести себя так же, так как Unionне может обнаружить дубликаты без кастома IEqualityComparer<X>. Он просто смотрит, являются ли оба одной и той же ссылкой.

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

Теперь вы можете использовать его в перегрузке Union:

var comparer = new XComparer();
a5 = lstX1.Cast<X>().Union(lstX2.Cast<X>(), new XComparer()); 
Тим Шмелтер
источник