如何在 C# 中高效合并两个数组并去除重复元素

在 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(),让代码为你自动完成繁琐的去重工作吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/19639.html
点赞
0.00 平均评分 (0% 分数) - 0