C# 从 List 中移除另一个集合

在 C# 中,从一个集合中移除另一个集合的元素,以及在不影响原集合的情况下返回新集合,有多种实现方式。我来详细解释并举例说明。

一、从 List 中移除另一个集合

1. 使用 RemoveAll + Contains(修改原集合)

csharp 复制代码
List<int> list1 = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> list2 = new List<int> { 2, 4, 6, 8, 10 };

// 移除 list1 中存在于 list2 的所有元素(修改原集合)
list1.RemoveAll(item => list2.Contains(item));

Console.WriteLine(string.Join(", ", list1)); // 输出:1, 3, 5, 7, 9

优点 :简单直接
缺点 :对每个元素都调用 Contains,大数据量时性能较差(O(n*m))

2. 使用 Except(返回新集合,不影响原集合)

csharp 复制代码
List<int> list1 = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> list2 = new List<int> { 2, 4, 6, 8, 10 };

// 返回新集合,原集合不变
List<int> result = list1.Except(list2).ToList();

Console.WriteLine("原集合:" + string.Join(", ", list1));  // 输出:1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Console.WriteLine("新集合:" + string.Join(", ", result)); // 输出:1, 3, 5, 7, 9

优点 :不影响原集合,使用 HashSet 优化,性能好(O(n+m))
缺点:会去重(如果原集合有重复元素,结果中只保留一个)

3. 使用 HashSet 优化性能(修改原集合)

csharp 复制代码
List<int> list1 = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> list2 = new List<int> { 2, 4, 6, 8, 10 };

HashSet<int> set = new HashSet<int>(list2);
list1.RemoveAll(item => set.Contains(item));

Console.WriteLine(string.Join(", ", list1)); // 输出:1, 3, 5, 7, 9

优点 :HashSet 的 Contains 是 O(1),大数据量性能好(O(n+m))
缺点:需要额外内存

二、返回新集合不影响原集合的多种方式

1. 使用 Except 方法(最简单)

csharp 复制代码
List<string> fruits = new List<string> { "苹果", "香蕉", "橙子", "葡萄", "西瓜", "香蕉" };
List<string> toRemove = new List<string> { "香蕉", "葡萄" };

// 返回新集合,原集合不变(但会去重)
List<string> result = fruits.Except(toRemove).ToList();

Console.WriteLine("原集合:" + string.Join(", ", fruits));  // 苹果, 香蕉, 橙子, 葡萄, 西瓜, 香蕉
Console.WriteLine("新集合:" + string.Join(", ", result)); // 苹果, 橙子, 西瓜

2. 使用 Where + Contains(保留重复元素)

csharp 复制代码
List<string> fruits = new List<string> { "苹果", "香蕉", "橙子", "葡萄", "西瓜", "香蕉" };
List<string> toRemove = new List<string> { "香蕉", "葡萄" };

// 返回新集合,原集合不变,保留重复元素
List<string> result = fruits.Where(f => !toRemove.Contains(f)).ToList();

Console.WriteLine("原集合:" + string.Join(", ", fruits));  // 苹果, 香蕉, 橙子, 葡萄, 西瓜, 香蕉
Console.WriteLine("新集合:" + string.Join(", ", result)); // 苹果, 橙子, 西瓜

3. 使用 HashSet 优化的版本(推荐)

csharp 复制代码
List<string> fruits = new List<string> { "苹果", "香蕉", "橙子", "葡萄", "西瓜", "香蕉" };
List<string> toRemove = new List<string> { "香蕉", "葡萄" };

// 使用 HashSet 提升性能
HashSet<string> removeSet = new HashSet<string>(toRemove);
List<string> result = fruits.Where(f => !removeSet.Contains(f)).ToList();

Console.WriteLine("原集合:" + string.Join(", ", fruits));  // 苹果, 香蕉, 橙子, 葡萄, 西瓜, 香蕉
Console.WriteLine("新集合:" + string.Join(", ", result)); // 苹果, 橙子, 西瓜

三、自定义对象的示例

csharp 复制代码
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

List<Person> employees = new List<Person>
{
    new Person { Id = 1, Name = "张三" },
    new Person { Id = 2, Name = "李四" },
    new Person { Id = 3, Name = "王五" },
    new Person { Id = 4, Name = "赵六" }
};

List<Person> toRemove = new List<Person>
{
    new Person { Id = 2, Name = "李四" },
    new Person { Id = 4, Name = "赵六" }
};

// 方法1:按Id移除
var removeIds = new HashSet<int>(toRemove.Select(p => p.Id));
List<Person> result = employees.Where(e => !removeIds.Contains(e.Id)).ToList();

// 方法2:使用 Except(需要实现 IEquatable<Person> 或传入比较器)
// 先实现比较器
public class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y) => x.Id == y.Id && x.Name == y.Name;
    public int GetHashCode(Person obj) => obj.Id.GetHashCode() ^ obj.Name.GetHashCode();
}

// 使用比较器
List<Person> result2 = employees.Except(toRemove, new PersonComparer()).ToList();

四、性能对比和选择建议

方法 是否修改原集合 性能 是否去重 适用场景
RemoveAll + Contains 是 O(n*m) 差 保留重复 数据量小
RemoveAll + HashSet 是 O(n+m) 好 保留重复 修改原集合
Except 否 O(n+m) 好 会去重 需要去重
Where + Contains 否 O(n*m) 差 保留重复 数据量小
Where + HashSet 否 O(n+m) 好 保留重复 推荐

推荐做法:

  • 需要修改原集合:RemoveAll + HashSet
  • 不需要修改原集合:Where + HashSet
  • 简单场景且数据量小:直接用 Except
相关推荐
Nebula_g1 小时前
JavaSE加强:Stream流
java·开发语言·windows·stream·javase·流
小羊没烦恼!2 小时前
系统内部模块(子系统)之间的耦合以及模块(子系统)划分
java·开发语言·前端·数据库·算法·c#
在角落发呆5 小时前
软件配置备份方法:从手动到自动化的实践指南
windows·安全
UIU1145 小时前
头文件可以防什么错?运用头文件做声明
c++·学习·c#
lingchen19065 小时前
Win 11 系统语言栏悬浮于桌面上影响操作感受,怎么取消?
windows
梅孔立6 小时前
Windows PowerShell 升级 Pi-Coding-Agent + OpenCode,解决 OpenCode 二进制无效报错
windows
东木月8 小时前
Windows C 盘清理指南:安全删除临时文件、缓存与 Prefetch
c语言·windows·安全
水饺编程9 小时前
第1章,[Win32 章节]:Windows 的方方面面
c语言·c++·windows·visual studio
超级大福宝10 小时前
VS Code Remote-SSH 因 RemoteForward 端口冲突导致连接失败的排查与解决
运维·服务器·windows·vscode·ssh
Vex2une10 小时前
Windows PowerShell 发展史:从命令行工具到自动化核心
windows·自动化运维·命令行·powershell·cmd·脚本语言·对象管道