你是否有在撸代码的时候,遇到求集合差集的情况,相信每个人都有机会遇到。我的第一想法是for循环逐个排查,但又觉着太笨了,相信CLR团队应该帮我们提供了更好的做法,于是在元数据的Enumerable类中翻了下所有的方法,密密麻麻好长一串!
鼠标一通滚动,几个关键词吸引了我
好家伙!!!备注里写着“Produces the set difference of two sequences by using the default equality comparer to compare values.”
这不就是我们要找的东西吗!
其实Except早在framework3.5就已经有了,但它只适用于单纯的两个集合或数组的求差集,比如:
想要在fruits1中求不在fruits2中的项,官方示例代码如下,很简单,一眼明了。
ProductA[] fruits1 = { new ProductA { Name = "apple", Code = 9 },
new ProductA { Name = "orange", Code = 4 },
new ProductA { Name = "lemon", Code = 12 } };
ProductA[] fruits2 = { new ProductA { Name = "apple", Code = 9 } };
// Get all the elements from the first array
// except for the elements from the second array.
IEnumerable<ProductA> except =
fruits1.Except(fruits2);
foreach (var product in except)
Console.WriteLine(product.Name + " " + product.Code);
/*
This code produces the following output:
orange 4
lemon 12
*/
但,如果想只根据其中 某个属性or字段 进行匹配,抑或多个属性or字段匹配,比如我把代码稍微调整:apple的code改为10,那么输出就不一样了:
//output
apple 10
orange 4
lemon 12
apple 10也被输出了!可我只想根据Name作为筛选条件。那么Except方法就不能满足了,ExceptBy应运而出,这个方法虽然在2021年底随着.net6 C#10发布就出来了,但一直没机会用,所以也不了解。有了它,我们就可以这样做了:
ProductA[] fruits1 = { new ProductA { Name = "apple", Code = 10 },
new ProductA { Name = "orange", Code = 4 },
new ProductA { Name = "lemon", Code = 12 } };
ProductA[] fruits2 = { new ProductA { Name = "apple", Code = 9 } };
static string KeySelector(ProductA pro) => pro.Name;
IEnumerable<ProductA> except =
fruits1.ExceptBy(fruits2.Select(KeySelector), KeySelector);
foreach (var product in except)
Console.WriteLine(product.Name + " " + product.Code);
//output
orange 4
lemon 12
至此,还不够,你可能会想,KeySelector只用了一个字段,如果多个怎么办?这时候,元组可以上场了!我们把KeySelector稍作调整:
static (string, int) KeySelector(ProductA pro) => (pro.Name, pro.Code);
//output
apple 10
orange 4
lemon 12
至此!它的介绍和使用方式就完了,一个词!优雅!
我们再看下它的源码:
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
if (first is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.first);
}
if (second is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second);
}
if (keySelector is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector);
}
return ExceptByIterator(first, second, keySelector, comparer);
}
private static IEnumerable<TSource> ExceptByIterator<TSource, TKey>(IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
var set = new HashSet<TKey>(second, comparer);
foreach (TSource element in first)
{
if (set.Add(keySelector(element)))
{
yield return element;
}
}
}
其大致逻辑就是,把集合2放入一个new HashSet,遍历集合1,如果可以成功插入集合2(不在集合2中),则yield return,这是个很好的做法。
随着C#10的发布,还有一些方法...By的方法推了出来: