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



如果我有两个类型的集合,以及一个比较其属性子集的iequalityComparer,则相交或联合的结果元素来自?

到目前为止我运行的测试建议以下内容:

  • Col1 Win的项目
  • 如果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);
    }

第一个集合应始终赢。

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 :正如Rawling在他的评论中提到的那样,MSDN在于Intersect的文档。我已经使用ILSpy查看了Intersect,它首先列举了第二个集合,也只有第一个集合,即使以相反的方式记录了第一个集合。

实际上,乔恩·斯基特(Jon Skeet)在edulinq中也提到了这种"谎言":http://msmvps.com/blogs/jon_skeet/archive/2010/2010/10/11/30/reimplementing-linq-to-objects-linq-to-objects-parts-parts-part-part-part-part-16-intersect-16-intersect-16-ingerect-16-ingerect-16-ingerect-16-ingerect-1-in-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;
}

最新更新