2014-01-24 2 views
3

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

тестов я добежит предлагают следующее:

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

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

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); 
    } 
+1

Я бы сказал, посмотрите на документацию MSDN, но на самом деле речь идет о 'Intersect'.Возможно, посмотрите [EduLinq] (http://msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx), он подробно рассказывает об исходных реализациях. – Rawling

+0

@ Rawling: интересно, ты прав. Я посмотрел на «Intersect» с помощью ILSpy, и сначала он перечисляет вторую коллекцию, а затем первый, даже если задокументирован наоборот. Что может быть причиной? ** Изменить ** На самом деле Джон Скит также упомянул эту «ложь»: http://msmvps.com/blogs/jon_skeet/archive/2010/12/30/reimplementing-linq-to-objects-part-16-intersect- и-build-fiddling.aspx (по его словам: _ «Это явно неверно». _) –

+0

@Tim. Также неоднозначно, какие элементы 'Intersect' возвращаются - я читал его как« маркирует элементы во второй последовательности и возвращает их ", но вы ясно читали это наоборот. (Да, это страница, о которой я думал, когда я связал EduLinq.) – Rawling

ответ

5

Первый сбор должен побеждать всегда.

MSDN:

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

Вот реализация 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 а):

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

Update: Как Ролинг упомянул в своем комментарии 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; 
} 
+0

Отличное объяснение. Я могу успокоиться, спасибо – trilson86

+0

@ trilson86: обновил мой ответ, но даже если 'Interersect' ведет себя иначе, чем задокументировано, он все равно вернет элемент из первой коллекции. –

+0

Это любопытная реализация - спасибо за указание. – trilson86

Смежные вопросы