让我们假设两个对象集合。我想检索第一个集合中未包含在第二个集合中的对象。
对于原始类型的集合,这很容易:
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<>
。
-
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()
的良好替代品吗?
答案 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));
(对不起代码格式化,在手机上。)