问题:Distinct()无法按字段过滤
\n\n想象一下:你有一个客户列表,其中混入了按邮箱地址重复的项。你写下:
\n\nvar uniqueClients = clients.Distinct().ToList();\n\n然后惊讶地发现重复项并没有消失。为什么?因为标准的Distinct()是按整个对象的引用(或者如果你重写了Equals,则按Equals)进行比较。它不知道对你来说,“唯一性”指的是唯一的Email,而不是唯一的类实例。
这个问题是Stack Overflow上关于LINQ最热门的问题之一(超过50万次浏览)。今天我们将介绍在C#中解决这个问题的3种实用方法。开始吧!
\n\n方法1:GroupBy + First(经典方法)
\n\n最简单易懂的方法,无需安装第三方库。思路:按所需属性对对象进行分组,然后从每个分组中取出第一个元素。
\n\nvar uniqueByEmail = clients\n .GroupBy(c => c.Email)\n .Select(g => g.First())\n .ToList();\n\n工作原理:
\n- \n
GroupBy(c => c.Email)— 将集合按邮箱地址作为键进行分组。 \n Select(g => g.First())— 从每个分组中取出第一个对象。 \n - 其余重复项被丢弃。 \n
优点:
\n- \n
- 开箱即用(只需
using System.Linq;)。 \n - 即使对于新手来说也直观易懂。 \n
- 保留原始元素顺序(第一个遇到的对象被保留)。 \n
缺点:
\n- \n
- 在处理大数据时稍慢(需要创建分组)。 \n
- 代码比期望的略显冗长。 \n
方法2:MoreLinq和DistinctBy(优雅之选)
\n\n如果你不介意使用第三方库,MoreLinq提供了完美的DistinctBy方法。它完全按照你对Distinct的期望工作——按属性过滤。
通过NuGet安装:
\nInstall-Package MoreLinq\n\n使用方法:
\nusing MoreLinq;\n\nvar uniqueByEmail = clients.DistinctBy(c => c.Email).ToList();\n\n一行代码,搞定一切。内部DistinctBy使用HashSet,时间复杂度为O(n)。
优点:
\n- \n
- 代码极其简洁且可读性强。 \n
- 高性能。 \n
- 该库经过充分测试(下载量超过1000万次)。 \n
缺点:
\n- \n
- 需要在项目中添加依赖。 \n
- 并非所有团队都允许使用第三方库。 \n
方法3:自定义IEqualityComparer(最大控制权)
\n\n当你不想引入外部库,但又希望在多个地方复用比较逻辑时,此方法适用。创建你自己的比较器:
\n\npublic class ClientEmailComparer : IEqualityComparer<Client>\n{\n public bool Equals(Client x, Client y)\n {\n if (ReferenceEquals(x, y)) return true;\n if (x is null || y is null) return false;\n return x.Email == y.Email;\n }\n\n public int GetHashCode(Client obj)\n {\n return obj.Email?.GetHashCode() ?? 0;\n }\n}\n\n使用方法:
\nvar comparer = new ClientEmailComparer();\nvar uniqueByEmail = clients.Distinct(comparer).ToList();\n\n优点:
\n- \n
- 完全控制比较逻辑。 \n
- 比较器可在
Contains、Except、Union等方法中复用。 \n - 无需外部库。 \n
缺点:
\n- \n
- 样板代码较多。 \n