C#中求两个集合的差集,Except和优雅的ExceptBy

你是否有在撸代码的时候,遇到求集合差集的情况,相信每个人都有机会遇到。我的第一想法是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的方法推了出来:

参考:设置操作 (C#) | Microsoft Learn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Boogaloo-Jer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值