您的位置:首页 > 手游攻略 > C#中List优雅去重的几种做法使用教程与实战指南实用指南

C#中List优雅去重的几种做法使用教程与实战指南实用指南

作者:互联网  时间: 2026-11-01 08:40:01  

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“C#中List优雅去重的几种做法”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

理解这一步时,在日常开发里,我们经常需处理集合中的重复数据。本文将全面介绍C#中List去重的各种优雅方式,帮助你在不同场景下选择最合适的方案。

前言

数据去重是数据处理中的常用需求,主要目的包括:

· 避免数据重复导致的统计错误
· 减少内存占用和提高性能
· 保证数据的唯一性约束
· 提升数据质量和用户体验

理解这一步时,本文将带你全面掌握C#中List去重的各种技巧,从基础用法到高级方案,应有尽有!

基础值类型去重

采用 LINQ 的 Distinct() 方法

// 整数去重
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
List<int> distinctNumbers = numbers.Distinct().ToList();
Console.WriteLine(string.Join(", ", distinctNumbers));
// 输出: 1, 2, 3, 4, 5

// 字符串去重
List<string> names = new List<string> { "Alice", "Bob", "Alice", "Charlie" };
List<string> distinctNames = names.Distinct().ToList();
Console.WriteLine(string.Join(", ", distinctNames));
// 输出: Alice, Bob, Charlie

自定义对象去重

首先定义示例类

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    
    public override string ToString() => $"Id: {Id}, Name: {Name}, Age: {Age}";
}

// 准备测试数据
List<Person> people = new List<Person>
{
    new Person { Id = 1, Name = "Alice", Age = 25 },
    new Person { Id = 1, Name = "Alice", Age = 26 }, // 重复ID
    new Person { Id = 2, Name = "Bob", Age = 30 },
    new Person { Id = 3, Name = "Charlie", Age = 35 }
};

方法一:重写 Equals 和 GetHashCode

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    
    // 基于Id去重
    public override bool Equals(object obj)
    {
        return obj is Person person && Id == person.Id;
    }
    
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

// 使用
List<Person> distinctPeople = people.Distinct().ToList();
foreach (var person in distinctPeople)
{
    Console.WriteLine(person);
}

方法二:采用自定义比较器(建议)

public class PersonComparer : IEqualityComparer<Person>
{
    private readonly bool _compareName;
    
    public PersonComparer(bool compareName = false)
    {
        _compareName = compareName;
    }
    
    public bool Equals(Person x, Person y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        
        if (_compareName)
            return x.Id == y.Id && x.Name == y.Name;
        else
            return x.Id == y.Id;
    }
    
    public int GetHashCode(Person obj)
    {
        if (_compareName)
            return HashCode.Combine(obj.Id, obj.Name);
        else
            return obj.Id.GetHashCode();
    }
}

// 使用自定义比较器
List<Person> distinctById = people.Distinct(new PersonComparer()).ToList();
List<Person> distinctByIdAndName = people.Distinct(new PersonComparer(true)).ToList();

Console.WriteLine("基于ID去重:");
foreach (var person in distinctById)
{
    Console.WriteLine($" {person}");
}

Console.WriteLine("基于ID和Name去重:");
foreach (var person in distinctByIdAndName)
{
    Console.WriteLine($" {person}");
}

高性能去重方案

采用 HashSet(建议用来大数据量)

// 基本值类型 - 最高效
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
HashSet<int> uniqueNumbers = new HashSet<int>(numbers);
List<int> distinctNumbers = uniqueNumbers.ToList();

// 自定义对象
HashSet<Person> uniquePeople = new HashSet<Person>(people, new PersonComparer());
List<Person> distinctPeople = uniquePeople.ToList();

// 内联比较器(无需定义类,简洁用法)
var distinctPeopleInline = new HashSet<Person>(people,
    EqualityComparer<Person>.Create((x, y) => x?.Id == y?.Id,
        x => x?.Id.GetHashCode() ?? 0))
    .ToList();

Console.WriteLine($"原始数据: {people.Count} 条");
Console.WriteLine($"去重后: {distinctPeople.Count} 条");

.NET 6+ 的新特性

采用 DistinctBy 方法(最优雅的方式)

// 按单个属性去重
List<Person> distinctById = people.DistinctBy(p => p.Id).ToList();

// 按多个属性去重
List<Person> distinctByIdAndName = people
    .DistinctBy(p => new { p.Id, p.Name })
    .ToList();

// 使用元组(性能更好)
List<Person> distinctByTuple = people
    .DistinctBy(p => (p.Id, p.Name))
    .ToList();

// 复杂条件去重
List<Person> distinctByCondition = people
    .DistinctBy(p => new {
        Category = p.Age < 30 ? "Young" : "Adult",
        p.Name
    })
    .ToList();

Console.WriteLine("使用DistinctBy按ID去重:");
foreach (var person in distinctById)
{
    Console.WriteLine($" {person}");
}

采用 GroupBy 方法

// 按ID分组取第一个
List<Person> distinctPeople = people
    .GroupBy(p => p.Id)
    .Select(g => g.First())
    .ToList();

// 按多个属性分组
List<Person> distinctByMultiple = people
    .GroupBy(p => new { p.Id, p.Name })
    .Select(g => g.First())
    .ToList();

// 取最后一个或特定元素
List<Person> distinctLast = people
    .GroupBy(p => p.Id)
    .Select(g => g.Last())
    .ToList();

// 按条件选择(如年龄最大的)
List<Person> distinctOldest = people
    .GroupBy(p => p.Id)
    .Select(g => g.OrderByDescending(p => p.Age).First())
    .ToList();

Console.WriteLine("取每个ID中年龄最大的:");
foreach (var person in distinctOldest)
{
    Console.WriteLine($" {person}");
}

优雅的扩展方法封装

新建扩展方法类

public static class ListExtensions
{
    /// <summary>
    /// 根据指定键选择器去重
    /// </summary>
    public static List<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
    {
        return source.DistinctBy(keySelector, null);
    }
    
    /// <summary>
    /// 根据指定键选择器和比较器去重
    /// </summary>
    public static List<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        ArgumentNullException.ThrowIfNull(source);
        ArgumentNullException.ThrowIfNull(keySelector);
        
        var seen = new HashSet<TKey>(comparer);
        return source.Where(item => seen.Add(keySelector(item))).ToList();
    }
    
    /// <summary>
    /// 高性能去重(使用HashSet)
    /// </summary>
    public static List<T> FastDistinct<T>(this IEnumerable<T> source)
    {
        return new HashSet<T>(source).ToList();
    }
    
    /// <summary>
    /// 高性能去重(使用HashSet和比较器)
    /// </summary>
    public static List<T> FastDistinct<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer)
    {
        return new HashSet<T>(source, comparer).ToList();
    }
}

采用扩展方法

// 使用扩展方法
var distinctById = people.DistinctBy(p => p.Id);
var distinctByName = people.DistinctBy(p => p.Name);
var distinctByMultiple = people.DistinctBy(p => new { p.Id, p.Name });

// 高性能去重
var fastDistinct = people.FastDistinct();
var fastDistinctWithComparer = people.FastDistinct(new PersonComparer());

Console.WriteLine("使用扩展方法去重:");
Console.WriteLine($"按ID去重: {distinctById.Count} 条记录");
Console.WriteLine($"高性能去重: {fastDistinct.Count} 条记录");

性能比较和选择指南

为便于帮助大家选择最佳方案,我整理了以下性能对比表格:

方法时间复杂度空间复杂度适用场景代码简洁性建议指数
HashSetO(n)O(n)大数据量,性能要求高
Distinct()O(n)O(n)一般场景
DistinctBy()O(n)O(n).NET 6+,按属性去重
GroupByO(n)O(n)需分组逻辑时
扩展方法O(n)O(n)代码复用和封装

轻松性能测试示例

public class PerformanceTest
{
    public static void TestDistinctMethods()
    {
        // 生成测试数据
        var largeList = Enumerable.Range(0, 100000)
            .Select(i => new Person { Id = i % 1000, Name = $"Person{i}" })
            .ToList();
        
        var stopwatch = Stopwatch.StartNew();
        
        // 测试不同方法
        var result1 = largeList.Distinct().ToList();
        Console.WriteLine($"Distinct(): {stopwatch.ElapsedMilliseconds}ms");
        stopwatch.Restart();
        
        var result2 = largeList.DistinctBy(p => p.Id).ToList();
        Console.WriteLine($"DistinctBy(): {stopwatch.ElapsedMilliseconds}ms");
        stopwatch.Restart();
        
        var result3 = new HashSet<Person>(largeList).ToList();
        Console.WriteLine($"HashSet: {stopwatch.ElapsedMilliseconds}ms");
        stopwatch.Restart();
        
        var result4 = largeList.GroupBy(p => p.Id).Select(g => g.First()).ToList();
        Console.WriteLine($"GroupBy: {stopwatch.ElapsedMilliseconds}ms");
    }
}

实际应用场景

场景一:数据库查询结果去重

// 模拟数据库查询结果
var databaseResults = new List<Person>
{
    new Person { Id = 1, Name = "Alice", Age = 25 },
    new Person { Id = 1, Name = "Alice", Age = 25 }, // 重复记录
    new Person { Id = 2, Name = "Bob", Age = 30 },
    new Person { Id = 3, Name = "Charlie", Age = 35 }
};

// 去重处理
var cleanResults = databaseResults
    .DistinctBy(p => p.Id)
    .ToList();

Console.WriteLine($"数据库查询去重: {databaseResults.Count} → {cleanResults.Count} 条记录");

场景二:API响应数据合并去重

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class ApiService
{
    public List<Product> MergeProducts(List<Product> localData, List<Product> apiData)
    {
        // 合并数据并去重
        var mergedData = localData
            .Concat(apiData)
            .DistinctBy(p => p.Id)
            .ToList();
            
        Console.WriteLine($"数据合并: {localData.Count} + {apiData.Count} → {mergedData.Count} 条记录");
        return mergedData;
    }
}

场景三:复杂业务逻辑去重

public class Order
{
    public int CustomerId { get; set; }
    public int ProductId { get; set; }
    public DateTime OrderDate { get; set; }
    public decimal TotalAmount { get; set; }
}

public class OrderService
{
    public List<Order> RemoveDuplicateOrders(List<Order> orders)
    {
        // 基于多个业务规则去重:同一客户、同一产品、同一天只保留金额最大的订单
        return orders
            .GroupBy(o => new { o.CustomerId, o.ProductId, o.OrderDate.Date })
            .Select(g => g.OrderByDescending(o => o.TotalAmount).First())
            .ToList();
    }
}

最佳实践和建议

选择策略

  1. 根据数据量选择方法:
    · 小数据量(< 1000):使用 Distinct() 或 DistinctBy()
    · 大数据量(≥ 1000):采用 HashSet
  2. 考虑比较逻辑复杂度:
    · 轻松比较:采用 Lambda 表达式
    · 复杂比较:采用自定义比较器
  3. 空值安全处理:
public class SafePersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (x is null && y is null) return true;
        if (x is null || y is null) return false;
        return x.Id == y.Id && x.Name == y.Name;
    }
    
    public int GetHashCode(Person obj)
    {
        return obj?.Id.GetHashCode() ?? 0;
    }
}
  1. 线程安全考虑:
    · 单线程:采用普通方法
    · 多线程:考虑采用 ConcurrentDictionary 或适当的同步机制

总结

结合项目来看,借助本文的学习,相信你已经掌握了C#中List去重的各种优雅方式。下面是我的终极选择建议:

实际处理时,· .NET 6+ 项目:优先采用 DistinctBy(),代码最简洁
· 高性能需求:采用 HashSet 构造器
· 复杂比较逻辑:采用自定义 IEqualityComparer
· 代码复用:封装为扩展方法
· 维护性:为自定义比较器添加单元测试

理解这一步时,总的来说,C# List 去重适合结合实际项目边做边理解。先抓住核心思路,再逐步补上细节和边界处理,最后效果会更稳定,也更容易复用。

相关资讯

最新游戏

更多

Copyright©2010-2019. All rights reserved | 波波三国游戏官网|[email protected]

备案编号:湘ICP备2022015115号-4