Приоритет сбора в LINQ Intersect, Union с использованием IEqualityComparer

Если у меня есть две коллекции типа T и IEqualityComparer, который сравнивает подмножество их свойств, из какой коллекции будут получены результирующие элементы Intersect или Union?

Тесты, которые я провел до сих пор, предполагают следующее:

  • предмет(ы) из col1 выигрывают
  • если col1 или col2 содержат повторяющиеся элементы (как определено компаратором) внутри себя, выигрывает первая запись (внутри col1, затем col2).

Я знаю, что это не должно быть проблемой, поскольку (по определению) я должен рассматривать полученные объекты как равные. Мне просто пришло в голову, что использование Union с пользовательским компаратором может быть немного более аккуратным, чем эквивалентное соединение, хотя это справедливо только в том случае, если вышеуказанные предположения гарантированы.

    class DummyComparer : IEqualityComparer<Dummy>
    {
        public bool Equals(Dummy x, Dummy y)
        {
            return x.ID == y.ID;
        }

        public int GetHashCode(Dummy obj)
        {
            return obj.ID.GetHashCode();
        }
    }

    class Dummy
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

    [Test]
    public void UnionTest()
    {
        var comparer = new DummyComparer();

        var d1 = new Dummy { ID = 0, Name = "test0" };
        var d2 = new Dummy { ID = 0, Name = "test1" };
        var d3 = new Dummy { ID = 1, Name = "test2" };
        var d4 = new Dummy { ID = 1, Name = "test3" };

        var col1 = new Dummy[] { d1, d3 };
        var col2 = new Dummy[] { d2, d4 };

        var x1 = col1.Union(col2, comparer).ToList();
        var x2 = col2.Union(col1, comparer).ToList();

        var y1 = col1.Except(col2, comparer).ToList();
        var y2 = col2.Except(col1, comparer).ToList();

        var z1 = col1.Intersect(col2, comparer).ToList();
        var z2 = col2.Intersect(col1, comparer).ToList();

        Assert.AreEqual(2, x1.Count);
        Assert.Contains(d1, x1);
        Assert.Contains(d3, x1);

        Assert.AreEqual(2, x2.Count);
        Assert.Contains(d2, x2);
        Assert.Contains(d4, x2);

        Assert.AreEqual(0, y1.Count);
        Assert.AreEqual(0, y2.Count);

        Assert.AreEqual(2, z1.Count);
        Assert.Contains(d1, z1);
        Assert.Contains(d3, z1);

        Assert.AreEqual(2, z2.Count);
        Assert.Contains(d2, z2);
        Assert.Contains(d4, z2);
    }

person trilson86    schedule 24.01.2014    source источник
comment
Я бы сказал, посмотрите документацию MSDN, но на самом деле она лжет о Intersect. Можете проверить EduLinq, он подробно описывает оригинал реализации.   -  person Rawling    schedule 24.01.2014
comment
@Роулинг: интересно, ты прав. Я просмотрел Intersect с помощью ILSpy, и он сначала перечисляет вторую коллекцию, а затем первую, даже если документировано наоборот. Что может быть причиной? Изменить На самом деле Джон Скит также упомянул эту ложь: msmvps.com/blogs/jon_skeet/archive/2010/12/30/ (по его словам: Это очевидно неверно.)   -  person Tim Schmelter    schedule 24.01.2014
comment
@Tim Также неясно, какие элементы Intersect возвращаются - я читаю это как элементы меток во второй последовательности и возвращаю их, но вы явно читаете наоборот. (Да, это страница, о которой я думал, когда связывал EduLinq.)   -  person Rawling    schedule 24.01.2014
comment
@Rawling: да, реализация не меняет поведение. Он все равно вернет объект из первой последовательности, даже если сначала перечислит вторую. Я обновил свой ответ. Сбивает с толку.   -  person Tim Schmelter    schedule 24.01.2014


Ответы (1)


Первая коллекция всегда должна побеждать.

MSDN:

Когда объект, возвращаемый этим методом, перечисляется, Union перечисляет первый и второй в указанном порядке и возвращает каждый элемент, который еще не был получен.

Вот реализация Union(ILSPY, .NET 4), первая коллекция нумеруется первой:

// System.Linq.Enumerable
private static IEnumerable<TSource> UnionIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    Set<TSource> set = new Set<TSource>(comparer);
    foreach (TSource current in first)
    {
        if (set.Add(current))
        {
            yield return current;
        }
    }
    foreach (TSource current2 in second)
    {
        if (set.Add(current2))
        {
            yield return current2;
        }
    }
    yield break;
}

То же самое относится к Intersect (и другие аналогичные методы в Linq-To-Objects):

Когда объект, возвращаемый этим методом, перечисляется, Intersect перечисляет первым, собирая все отдельные элементы этой последовательности. Затем он перечисляет второй, отмечая те элементы, которые встречаются в обеих последовательностях. Наконец, отмеченные элементы выдаются в том порядке, в котором они были собраны.

Обновление: как упомянул Роулинг в своем комментарии, MSDN находится в документации Intersect. Я просмотрел Intersect с ILSpy, и он сначала перечисляет вторую коллекцию, а затем первую, даже если документировано наоборот.

На самом деле Джон Скит также упомянул эту «ложь» в EduLinq: http://msmvps.com/blogs/jon_skeet/archive/2010/12/30/reimplementing-linq-to-objects-part-16-intersect-and-build-fiddling.aspx (в его слова: "Это явно неверно.")

Однако, даже если он реализован не так, как ожидалось, он все равно вернет элемент первой коллекции, как вы можете видеть в реализации:

// System.Linq.Enumerable
private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    Set<TSource> set = new Set<TSource>(comparer);
    foreach (TSource current in second)
    {
        set.Add(current);
    }
    foreach (TSource current2 in first)
    {
        if (set.Remove(current2))
        {
            yield return current2;
        }
    }
    yield break;
}
person Tim Schmelter    schedule 24.01.2014
comment
@trilson86: обновил мой ответ, но даже если Intersect ведет себя иначе, чем задокументировано, он все равно вернет элемент из первой коллекции. - person Tim Schmelter; 24.01.2014
comment
Это любопытная реализация - спасибо, что указали на это. - person trilson86; 24.01.2014