LINQ Intersect,Union中的集合优先级,使用IEqualityComparer

tri*_*n86 4 c# linq union iequalitycomparer intersect

如果我有两个类型为T的集合,以及一个比较其属性子集的IEqualityComparer,那么Intersect或Union的结果元素来自哪个集合?

我到目前为止进行的测试表明如下:

  • 来自col1 win的项目
  • 如果col1或col2本身包含重复项(由比较器定义),则第一个条目(在col1中,然后是col2)获胜.

我知道这不应该是一个问题,因为(根据定义)我应该将结果对象视为相等.我刚刚想到,使用带有自定义比较器的Union可能比等效的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);
    }
Run Code Online (Sandbox Code Playgroud)

Tim*_*ter 7

第一个系列应该永远赢.

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;
}
Run Code Online (Sandbox Code Playgroud)

这同样适用于Intersect(以及其他类似的方法Linq-To-Objects):

枚举此方法返回的对象时,Intersect 首先枚举,收集该序列的所有不同元素.然后它枚举第二个,标记在两个序列中出现的那些元素.最后,标记的元素按照它们被收集的顺序产生.

更新:正如Rawling在他的评论中提到的,MSDN在于文档Intersect.我已经看过IntersectILSpy它,它首先列举了第二个集合,然后才是第一个集合,即使是相反的记录.

实际上Jon Skeet也在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;
}
Run Code Online (Sandbox Code Playgroud)