Linq - 更好的`Enumerable.Except()`运算符(性能和灵活性)?

时间:2013-06-18 11:14:35

标签: .net performance linq

让我们假设两个对象集合。我想检索第一个集合中未包含在第二个集合中的对象。

对于原始类型的集合,这很容易:

new[]{1,2,3,4}.Except(new[]{2,3}); //  => {1, 4}

但是如果我想使用更复杂的结构呢?在下面的示例中,我想使用Id字段进行比较。

class Person { string Name; int Id ; }

var lst1 = new[]{ new Person("Ann", 1), new Person("Bob", 2) };
var lst2 = new[]{ new Person("Cathy", 3), new Person("Bob", 2) };

嗯,普遍的共识似乎提供了这两个选项:

  • Enumerable.Except()加上自定义IEqualityComparer<>,按照以下几行:

-

class IdComparer: IEqualityComparer<Person> { /* boilerplate Equals(), GetHashCode() */ }

lst1.Except(lst2, new IdComparer())
    .Select(p=>p.Name);              // => { "Ann" } 

这种方法对于定义相等标准很麻烦。

  • 使用否定的.Contains() - 仍然需要IEqualityComparer<>;或者否定.Any() - 这允许指定内联条件。

-

from p1 in lst1
where ! lst2.Any(p2 => p1.Id == p2.Id)
select p1.Name;                      // => { "Ann" } 

这更容易使用,但它读起来像“对于lst1中的每个元素检查lst2中的每个元素”,它看起来像复杂度O(M * N)。不确定不同的Linq提供商(可以)是否会对此进行优化。

复杂性方面,.Except()方法的表现要好一些:大致为O(M + N),as it uses a Set<>

  • Sql的'left-outer-join-filtered-by-NULLs'技巧怎么样?我没有找到对这个的引用,所以要么我没有足够的搜索,要么它有缺陷。

-

from p1 in lst1
join p2 in lst2 on p1.Id equals p2.Id into grp
where ! grp.Any()
select p1.Name;                     // => { "Ann" }

这样可以使用字段轻松比较 另外,根据我的判断(深入研究Enumerable.JoinIterator()实现),复杂性仍大致为O(M + N)。

这是Enumerable.Except()的良好替代品吗?

2 个答案:

答案 0 :(得分:4)

您可以使用moreLINQ库中的ExceptBy扩展名

它允许您指定用于比较的键:

public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector)

甚至指定相等比较器:

public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer)

答案 1 :(得分:0)

我有一个解决方案,使用Except。

看看这个:

public class PropertyEqualityComparer<TObject, TProperty> 
    : IEqualityComparer<TObject>
{
    Func<TObject, TProperty> _selector;
    IEqualityComparer<TProperty> _internalComparer;
    public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector,
        IEqualityComparer<TProperty> innerEqualityComparer = null)
    {
        _selector = propertySelector;
        _internalComparer = innerEqualityComparer;
    }
    public int GetHashCode(TObject obj)
    {
        return _selector(obj).GetHashCode();
    }
    public bool Equals(TObject x, TObject y)
    {
        IEqualityComparer<TProperty> comparer = 
            _internalComparer ?? EqualityComparer<TProperty>.Default;
        return comparer.Equals(_selector(x), _selector(y));
    }
}
public static class PropertyEqualityComparer
{
    public static PropertyEqualityComparer<TObject, TProperty>
        GetNew<TObject, TProperty>(Func<TObject, TProperty> propertySelector)
    { 
        return new PropertyEqualityComparer<TObject, TProperty>
            (propertySelector);
    }
    public static PropertyEqualityComparer<TObject, TProperty>
        GetNew<TObject, TProperty>
        (Func<TObject, TProperty> propertySelector, 
        IEqualityComparer<TProperty> comparer)
    { 
        return new PropertyEqualityComparer<TObject, TProperty>
            (propertySelector, comparer);
    }
}

基本上它的作用是让你拥有一个可以使用选择器进行比较的IEqualityComparer。然后你可以像这样使用它:

lst1.Except(lst2, PropertyEqualityComparer.GetNew(n => n.Id));

(对不起代码格式化,在手机上。)