.NET 中如何快速实现 List 集合去重?

前言

在数据处理中,去除集合中的重复元素是一个常见的需求。.NET 6 和 .NET 7 引入了 DistinctBy 方法,这是一个非常实用的新特性,可以方便地根据指定的键对集合进行去重。

本文将详细介绍 DistinctBy 方法的使用,并通过具体的案例来展示其在实际开发中的应用。

正文

1、DistinctBy 方法

DistinctBy 方法允许我们在 LINQ 查询中根据某个键对集合中的元素进行去重。

这个方法返回一个新的集合,其中只包含根据指定键唯一确定的元素。

方法签名

复制代码
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector
);

2、基本用法

最简单的用法是在 LINQ 查询中直接调用 DistinctBy 方法,然后处理去重后的集合。

说明

假设我们有一个用户列表,我们想要根据用户名去除重复的用户。

复制代码
using System.Linq;
​
class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}
​
var users = new List<User>
{
    new User { Name = "Alice", Age = 25 },
    new User { Name = "Bob", Age = 32 },
    new User { Name = "Alice", Age = 28 },
    new User { Name = "David", Age = 35 }
};
​
var distinctUsers = users.DistinctBy(user => user.Name);
​
foreach (var user in distinctUsers)
{
    Console.WriteLine($"Name: {user.Name}, Age: {user.Age}");
}

输出结果:

复制代码
Name: Alice, Age: 25
Name: Bob, Age: 32
Name: David, Age: 35

过滤前后元素还是保持原有的顺序,我们可以查看源码。

源码

复制代码
private static IEnumerable<TSource> DistinctByIterator<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
    using IEnumerator<TSource> enumerator = source.GetEnumerator();
​
    if (enumerator.MoveNext())
    {
        var set = new HashSet<TKey>(DefaultInternalSetCapacity, comparer);
        do
        {
            TSource element = enumerator.Current;
            if (set.Add(keySelector(element)))
            {
                yield return element;
            }
        }
        while (enumerator.MoveNext());
    }
}

通过查看源码,可以看到是利用了 HashSet 去重,元素顺序并未被打乱。

在处理集合时,我们经常需要去除重复的元素,同时保持原有的顺序。

使用 HashSet 可以高效地实现这一目标。

首先将指定的键尝试添加到 HashSet 中,如果添加成功,说明该键没有重复;

如果添加失败,说明已经存在相同的键,此元素将被过滤掉。

3、复杂用法

DistinctBy 方法可以用于更复杂的去重逻辑,例如根据多个属性进行去重。

说明

假设我们有一个订单列表,我们想要根据客户名称和订单金额去除重复的订单。

复制代码
class Order
{
    public int OrderId { get; set; }
    public string CustomerName { get; set; }
    public decimal Amount { get; set; }
}
​
var orders = new List<Order>
{
    new Order { OrderId = 1, CustomerName = "Alice", Amount = 100.0m },
    new Order { OrderId = 2, CustomerName = "Bob", Amount = 150.0m },
    new Order { OrderId = 3, CustomerName = "Alice", Amount = 100.0m },
    new Order { OrderId = 4, CustomerName = "Charlie", Amount = 120.0m },
    new Order { OrderId = 5, CustomerName = "Bob", Amount = 150.0m }
};
​
var distinctOrders = orders.DistinctBy(order => (order.CustomerName, order.Amount));
​
foreach (var order in distinctOrders)
{
    Console.WriteLine($"Order ID: {order.OrderId}, Customer: {order.CustomerName}, Amount: {order.Amount}");
}

输出结果:

复制代码
Order ID: 1, Customer: Alice, Amount: 100.0
Order ID: 2, Customer: Bob, Amount: 150.0
Order ID: 4, Customer: Charlie, Amount: 120.0

4、性能考虑

DistinctBy 方法在内部使用哈希表来跟踪已经出现的键,因此在大多数情况下性能非常好。但在处理非常大的数据集时,仍然需要注意内存使用情况。

说明

假设我们有一个包含数百万条记录的大集合,我们需要根据某个键进行去重。

复制代码
var largeCollection = Enumerable.Range(1, 10000000).Select(i => new { Id = i, Value = i % 1000 });
var distinctLargeCollection = largeCollection.DistinctBy(item => item.Value);
Console.WriteLine($"Distinct count: {distinctLargeCollection.Count()}");

5、异步 LINQ 查询中的使用

DistinctBy 方法也可以在异步 LINQ 查询中使用,结合 IAsyncEnumerable<T> 类型,处理大量数据时更加高效。

说明

假设我们有一个异步方法返回一个用户列表,我们想要根据用户名去除重复的用户。

复制代码
using System.Net.Http.Json;
​
public async IAsyncEnumerable<User> GetUsersAsync()
{
    var response = await httpClient.GetAsync("https://api.example.com/users");
    var usersJson = await response.Content.ReadAsStringAsync();
    
    // 使用Json序列化工具解析用户列表
    var users = JsonSerializer.Deserialize<List<User>>(usersJson);
    
    foreach (var user in users)
    {
        yield return user;
    }
}
​
// 使用异步LINQ查询
var distinctUsers = await GetUsersAsync().DistinctByAsync(user => user.Name).ToListAsync();
​
foreach (var user in distinctUsers)
{
    Console.WriteLine($"Name: {user.Name}, Age: {user.Age}");
}

总结

DistinctBy 方法是 .NET 6 和 .NET 7 中 LINQ 的一个非常实用的新特性。我们在 LINQ 查询中根据指定的键对集合进行去重,简化了代码并提高了开发效率。

希望本文能帮助大家更好地理解和利用 .NET 6 和 .NET 7 中 LINQ 的 DistinctBy 方法,从而在项目中发挥更大的作用。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

相关推荐
xb11322 小时前
C#生产者-消费者模式
开发语言·c#
今晚打老虎z2 小时前
解决SQL Server 安装运行时针对宿主机内存不足2GB的场景
sqlserver·c#
Traced back3 小时前
# C# WinForms 数据库清理系统基础知识与避坑指南
开发语言·数据库·c#
我要打打代码5 小时前
关于C#线程 任务
开发语言·数据库·c#
Traced back6 小时前
# C# 基础语法完全指南
开发语言·c#
大黄说说6 小时前
TensorRTSharp 实战指南:用 C# 驱动 GPU,实现毫秒级 AI 推理
开发语言·人工智能·c#
芳草萋萋鹦鹉洲哦7 小时前
后端C#,最好能跨平台,桌面应用框架如何选择?
开发语言·c#
kylezhao20197 小时前
C#中开放 - 封闭原则(**Open-Closed Principle,OCP**)
服务器·c#·开闭原则
百锦再9 小时前
《C#上位机开发从门外到门内》2-7:网络通信(TCP/IP、UDP)
tcp/ip·udp·c#·嵌入式·上位机·通信·下位机