在 C# 的日常开发中,处理数据集合是再常见不过的任务了。作为开发者,你一定遇到过这样的场景:从两个不同的数据源获取了两批数据,现在需要把它们整合在一起,但要求结果必须是唯一的——也就是说,如果两个数组中包含相同的元素,在最终的结果里它只能出现一次。
在 2026 年的今天,随着 AI 辅助编程的普及和云原生架构的深入,虽然我们编写代码的方式正在发生变化(比如越来越依赖 Cursor 或 GitHub Copilot 这样的智能助手),但底层的高效数据处理逻辑依然是构建高性能应用的基石。今天,我们将深入探讨一种非常优雅且高效的方法来实现这一目标。我们不仅会学习如何“合并”数组,更重要的是如何在这个过程中“去重”。我们将重点介绍 Union() 方法,并对比其他可能的实现方式,帮助你写出更简洁、更专业的代码。无论你是处理简单的整数列表,还是复杂的对象集合,这篇文章都将为你提供实用的见解。
为什么去重合并如此重要?
在实际应用中,数据合并通常伴随着数据清洗。例如,你可能正在合并两个不同时间段的用户列表,或者整合来自不同第三方供应商的产品 ID 列表。如果不进行去重,后续的业务逻辑(如发送通知、计费或统计)就可能会因为重复数据而产生错误。在我们最近的一个金融科技项目中,我们曾遇到因为合并交易日志时未去重,导致风控模型误判的情况,这让我们深刻意识到:数据的唯一性是系统稳定性的第一道防线。
核心方法:使用 LINQ 的 Union()
在 C# 中,最直接、最现代的方法是使用 LINQ(Language Integrated Query)提供的 Union() 扩展方法。这个方法的设计初衷就是为了解决“集合并集”的问题。到了 2026 年,随着 C# 13/14 的迭代,LINQ 的性能已经优化到了极致,配合 Span 和高性能集合,它的表现依然无可匹敌。
#### 语法简介
使用 Union() 非常简单,它的基本语法如下:
// 这是一个延迟执行的查询
var result = array1.Union(array2);
// 如果需要立即结果(通常在返回数据或序列化时)
var finalArray = array1.Union(array2).ToArray();
这里,INLINECODEcb07f48a 是调用者,INLINECODE040cb50b 是参数。INLINECODEe3d83791 方法会返回一个包含两个数组中所有唯一元素的新序列。需要注意的是,默认情况下,INLINECODE56ab44f0 返回的是一个 INLINECODE51bc8e8a 类型的延迟执行查询。如果你需要立即得到一个数组,通常会在后面链式调用 INLINECODE9f9edcdd 方法。
#### 工作原理
Union() 方法背后的逻辑是基于集合论中的“并集”概念。它的工作步骤大致如下:
- 遍历第一个集合:首先取出第一个数组中的所有元素。
- 遍历第二个集合:然后取出第二个数组的元素。
- 去重过滤:这是最关键的一步。当添加来自第二个数组的元素时,如果该元素已经存在于第一个数组的结果中,它将被自动忽略。
- 返回结果:最终返回一个包含所有唯一元素的序列。
此外,INLINECODE0486f864 不仅能去除两个数组之间的重复,如果单个数组内部本身就有重复值(例如 INLINECODE8c3d59e2),该方法也会一并处理,确保最终结果中每个值只出现一次。这其实是因为它内部使用了类似 HashSet 的机制来保证唯一性。
代码实战:从基础到进阶
为了让你更全面地掌握这个技巧,让我们通过几个具体的例子来演示。我们将从最基础的整数数组开始,逐步过渡到字符串数组,并讨论一些实际开发中的注意事项。
#### 示例 1:合并整数数组(最基础场景)
在这个例子中,我们有两个包含整数的数组。我们可以看到,INLINECODEb7897d9c 和 INLINECODE1b2c3560 这两个数字在两个数组中都出现了。我们的目标是得到一个合并后的列表,这些数字只出现一次。
using System;
using System.Linq;
class Program
{
static void Main()
{
// 声明第一个整数数组
int[] array1 = { 22, 33, 21, 34, 56, 32 };
// 声明第二个整数数组,其中包含一些与 array1 重复的数字
int[] array2 = { 24, 33, 21, 34, 22 };
Console.WriteLine("Array 1 的元素:");
foreach (int x1 in array1)
{
Console.WriteLine(x1);
}
Console.WriteLine("
Array 2 的元素:");
foreach (int x2 in array2)
{
Console.WriteLine(x2);
}
// 使用 Union 方法合并数组,并立即转换为数组
// 这里的 Union 会自动过滤掉重复的 33, 21, 34, 22
var finalArray = array1.Union(array2).ToArray();
// 遍历并显示最终的合并结果
Console.WriteLine("
合并去重后的新数组:");
foreach (int item in finalArray)
{
Console.WriteLine(item);
}
}
}
输出结果:
Array 1 的元素:
22
33
21
34
56
32
Array 2 的元素:
24
33
21
34
22
合并去重后的新数组:
22
33
21
34
56
32
24
你可以看到,结果列表中保留了所有出现过的数字,但没有任何重复。这是一个非常干净的处理方式。
#### 示例 2:合并字符串数组
处理整数很简单,但处理字符串时情况也一样吗?是的,Union() 对字符串类型同样有效,因为它会使用默认的字符串比较器来判断内容是否相同。
using System;
using System.Linq;
class Program
{
static void Main()
{
// 定义第一个字符串数组(例如:第一批用户名)
string[] usersGroup1 = { "Alice", "Bob", "Charlie" };
// 定义第二个字符串数组(例如:第二批用户名,包含一些重叠)
string[] usersGroup2 = { "David", "Alice", "Bob" };
Console.WriteLine("用户组 1:");
DisplayArray(usersGroup1);
Console.WriteLine("
用户组 2:");
DisplayArray(usersGroup2);
// 使用 Union 合并
var uniqueUsers = usersGroup1.Union(usersGroup2).ToArray();
Console.WriteLine("
去重后的完整用户列表:");
DisplayArray(uniqueUsers);
// 辅助方法
static void DisplayArray(string[] arr)
{
foreach (var item in arr)
{
Console.WriteLine(item);
}
}
}
}
输出结果:
用户组 1:
Alice
Bob
Charlie
用户组 2:
David
Alice
Bob
去重后的完整用户列表:
Alice
Bob
Charlie
David
在这个例子中,"Alice" 和 "Bob" 虽然在两个列表中都有,但在最终结果中只出现了一次。
#### 示例 3:进阶技巧——忽略大小写的比较
在实际业务中,字符串的大小写往往不应该被视为不同的元素。比如 "User1" 和 "user1" 可能指向同一个账号。然而,标准的 INLINECODEf7b5f289 默认是区分大小写的(基于 INLINECODE818bbea2)。
如果我们希望合并字符串时忽略大小写,我们需要使用 INLINECODE0d52ad15 的另一个重载版本,传入一个自定义的 INLINECODE8abce739。好在 .NET 为我们提供了现成的 StringComparer。
using System;
using System.Linq;
class Program
{
static void Main()
{
string[] tags1 = { "csharp", "tutorial", "coding" };
string[] tags2 = { "Coding", "CSharp", "Programming" };
// 标准的 Union() 会认为 "coding" 和 "Coding" 是不同的
var caseSensitiveResult = tags1.Union(tags2).ToArray();
Console.WriteLine("区分大小写的结果 (默认行为):");
Console.WriteLine(string.Join(", ", caseSensitiveResult));
// 我们使用 StringComparer.OrdinalIgnoreCase 来实现不区分大小写的合并
var caseInsensitiveResult = tags1.Union(tags2, StringComparer.OrdinalIgnoreCase).ToArray();
Console.WriteLine("
不区分大小写的结果:");
Console.WriteLine(string.Join(", ", caseInsensitiveResult));
}
}
这个技巧在处理用户输入、标签系统或日志分析时非常有用,能显著提高数据清洗的准确性。
#### 示例 4:使用 IEnumerable 延迟执行查询
有时候,我们不需要立即得到一个数组,而是希望得到一个可以在后续逻辑中进一步处理的查询结果。INLINECODEef2d2956 返回的是 INLINECODEc7643101,这意味着它支持延迟执行。这在处理大型数据集或组合多个 LINQ 操作时可以提高性能。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 3, 4, 5 };
// 这里并没有立即执行计算或分配新的数组内存
// 只是定义了一个查询逻辑
IEnumerable query = numbers1.Union(numbers2);
// 我们可以在这个查询的基础上继续进行 LINQ 操作
// 例如:只筛选出大于 2 的数字
var finalQuery = query.Where(n => n > 2);
Console.WriteLine("最终筛选后的结果:");
// 只有在真正遍历时,代码才会真正执行
foreach (var num in finalQuery)
{
Console.WriteLine(num);
}
}
}
输出结果:
3
4
5
替代方案:Concat + Distinct
除了 INLINECODEa7774488,C# 中还有另一种常见的组合方式:先使用 INLINECODE2fd1707b 连接两个数组,再使用 Distinct() 去重。
var result = array1.Concat(array2).Distinct().ToArray();
这和 Union() 有区别吗?
从最终结果来看,通常是一样的。但从语义和性能上看,Union() 更好。
- 语义清晰:INLINECODEe1a29590 明确表达了“求并集”的意图,而 INLINECODEb1e821d2 +
Distinct描述的是操作步骤(先连起来再去重)。 - 性能优化:在某些 LINQ 提供程序(如数据库查询 Entity Framework)中,INLINECODEa94d2fbf 可能会被优化成一条 SQL 中的 INLINECODE62c764b1 操作符,效率通常高于生成 INLINECODE90b0dbaa 后再 INLINECODE729cb382。对于内存中的数组集合,虽然差异不大,但 INLINECODEb6ce326d 直接构建唯一性集合,而 INLINECODE3e018051 会先创建一个包含所有项(含重复)的中间枚举,理论上
Union的内存模型更直接。
因此,推荐优先使用 Union()。
2026 年开发视角:处理复杂对象与现代化陷阱
随着业务逻辑的复杂化,我们更多时候是在处理对象列表而非简单的整数。如果你正在使用最新的 C# 特性(如记录类型 INLINECODEb390b99f 或主构造函数),你会发现 INLINECODE1428aeef 的行为变得更加智能,但也有一些需要特别注意的地方。
#### 1. 引用类型的陷阱
如果你合并的不是简单的整数或字符串,而是自定义的类,那么 Union() 默认比较的是对象的引用(内存地址),而不是对象的内容。
// 定义一个简单的类
class User { public int Id { get; set; } public string Name { get; set; } }
// ... in Main
User u1 = new User { Id = 1, Name = "A" };
User[] list1 = { u1 };
// 这是一个新的实例,内容完全一样,但在内存中是不同的对象
User[] list2 = { new User { Id = 1, Name = "A" } };
// 结果会有两个元素!因为默认比较器只看引用地址
var merged = list1.Union(list2).ToList();
解决方案 A:使用 record 类型(推荐)
在 2026 年,我们更推荐使用 INLINECODE99cc08ef 类型。编译器会自动为你生成基于值的相等性判断(INLINECODEc935a407 和 INLINECODE10833e5c),使得 INLINECODE64559161 能够直接开箱即用。
// 使用 record,编译器自动实现值相等性
public record User(int Id, string Name);
// 现在 Union 会自动按值去重,非常智能
User[] list1 = { new User(1, "A") };
User[] list2 = { new User(1, "A") };
var merged = list1.Union(list2).ToList(); // 结果只有 1 个元素
解决方案 B:使用 .UnionBy()(.NET 6+ 现代写法)
如果你不能更改类定义,或者你需要根据特定属性(比如只按 INLINECODE3d732e5c 去重,忽略 INLINECODEfc6a7d7f),最优雅的方式是使用 UnionBy。这是现代 C# 开发中最常用的技巧之一。
// 即使是普通的 Class,也可以通过 UnionBy 指定 "键" 进行去重
// 这里指定 user.Id 作为判断唯一性的依据
var merged = list1.UnionBy(list2, user => user.Id).ToList();
2. 常见误区与最佳实践
在使用 Union() 合并数组时,有几个坑是你可能会踩到的,让我们提前了解一下。
#### 顺序敏感性问题
虽然 Union 是数学上的并集(数学上集合是无序的),但在 C# 中,它保留了元素出现的顺序。它会先返回第一个数组中出现的元素(按原顺序),然后追加第二个数组中未被包含的元素(按原顺序)。
- array1:
{ 2, 1 } - array2:
{ 1, 3 } - Result:
{ 2, 1, 3 }
注意结果中 INLINECODEe7f27c49 的位置紧随 INLINECODE98567e55 之后,来自 array1。这是一个非常棒的特性,保证了数据的一致性,尤其是在你需要处理带有时间戳或优先级的数据流时。
#### 空数组处理
INLINECODEe773e45f 对空数组处理得非常完美,你不需要做额外的 null 检查(只要数组本身不是 null)。如果 INLINECODEf055c875 是空的,结果就是 INLINECODEf80fdad7 的去重版本。这减少了大量的 INLINECODEd9edbb93 判断代码,符合现代敏捷开发的简洁原则。
性能优化建议与 AI 辅助开发
对于几千个元素的小型数组,Union() 的性能非常快,无需担心。但是,如果你在处理包含数百万条数据的超大数组,性能就需要纳入考量了。
INLINECODE066d7966 方法在内部使用 INLINECODE003ba53c 来跟踪已存在的元素。这意味着它的算法时间复杂度接近于线性时间 O(N)。它需要为元素分配哈希表的内存。如果是在极低内存的环境下处理海量数据,请注意内存压力。但在绝大多数 C# 企业级应用中,Union() 都是最佳选择。
#### AI 辅助开发的小贴士
在使用像 GitHub Copilot 或 Cursor 这样的 AI 工具时,如果你输入 INLINECODEc083f8ef,AI 通常会直接建议 INLINECODE0580eca5。然而,作为经验丰富的开发者,我们需要审查生成的代码:
- 审查隐式类型:AI 经常使用 INLINECODEdb2f81da。确保在处理复杂对象时,你明确知道返回的是 INLINECODEe4098c27 还是具体的数组。
- 检查比较器:AI 有时会忘记处理 INLINECODE60bb9fdd 值的大小写比较。如果你看到生成的代码中 INLINECODEf84049fc 没有带
StringComparer,记得根据业务需求确认是否需要添加。 - 复杂度警告:如果你在一个循环中进行多次
Union操作(比如 N 次合并),AI 可能会写出 O(N^2) 的代码。这时我们需要手动优化为一次性合并多个集合。
总结
在这篇文章中,我们深入探讨了如何在 C# 中合并两个数组并去除重复值。我们发现,使用 LINQ 的 Union() 方法是解决此类问题的银弹。它不仅代码简洁、可读性强,而且在底层实现上也非常高效。
我们涵盖了以下关键点:
- 基本用法:使用
array1.Union(array2)快速合并。 - 类型支持:对整数、字符串等基本类型的无缝支持。
- 高级技巧:利用
StringComparer实现忽略大小写的合并。 - 原理分析:了解了 INLINECODE99f3d999 与 INLINECODEa270cbb8 的细微差别,以及
Union如何保留元素顺序。 - 对象处理:利用 INLINECODE755f7fe7 类型或 INLINECODE87788631 解决引用类型去重难题。
- 现代视角:结合 AI 编程工具的最佳实践。
掌握了这些技巧,你可以在处理数据清洗、日志合并或配置整合时更加得心应手。下次当你面对两个杂乱的数组时,不妨试试 Union(),让代码为你自动完成繁琐的去重工作吧!