ZetCode

C# ArraySegment

最后修改日期:2025 年 4 月 27 日

本教程解释了如何在 C# 中使用 ArraySegment 结构,以便高效地处理数组的各个部分。ArraySegment 提供了一个数组片段的视图,而无需复制。

ArraySegment 结构代表数组的一个片段,该片段由起始偏移量和元素计数指定。此结构提供了一种处理现有数组中连续元素子集的方法,而无需创建新数组。通过维护对原始数组的引用,它可以有效地操作和处理其特定部分,同时避免与复制数据相关的开销。这使得 ArraySegment 成为在内存密集型应用程序中管理数组子集的轻量级且灵活的工具。

ArraySegment 在需要处理数组部分的情况下特别有用,例如解析结构化数据、缓冲大量信息流或实现用于数据处理的自定义算法。它直接引用数组一部分的能力,而不是创建副本,显著减少了内存使用量,尤其是在处理大型数据集时。

此外,ArraySegment 保留了类型安全性,确保开发人员可以使用强类型数据,同时避免与手动切片或自定义子数组处理相关的错误。它是一个有价值的实用程序,可以提高操作数组片段的代码的性能和清晰度。

基本 ArraySegment 示例

此示例演示了如何从数组创建 ArraySegment,并在不复制数据的情况下访问其元素。

Program.cs
int[] numbers = [1, 2, 3, 4, 5];
ArraySegment<int> segment = new(numbers, 1, 3);

Console.WriteLine($"First: {segment[0]}");
Console.WriteLine($"Count: {segment.Count}");
Console.WriteLine($"Array reference: {segment.Array == numbers}");

该程序从一个整数数组创建一个 ArraySegment,从索引 1 开始,包含 3 个元素。它演示了如何访问第一个元素,检查片段的计数,并验证该片段引用原始数组。ArraySegment 构造函数指定数组、偏移量和计数,从而创建一个视图而不进行复制。对片段的修改会影响原始数组,因为它们共享相同的内存,从而使其高效地操作数组子集。

切片 ArraySegment

ArraySegment 可以进一步切片以创建更小的片段。此示例演示了如何创建子片段。

Program.cs
int[] numbers = [1, 2, 3, 4, 5];
ArraySegment<int> segment = new(numbers, 1, 4);
ArraySegment<int> subSegment = segment.Slice(1, 2);

Console.WriteLine($"Sub-segment: {string.Join(", ", subSegment.ToArray())}");
Console.WriteLine($"Sub-segment count: {subSegment.Count}");

该示例从一个数组创建一个 ArraySegment,然后使用 Slice 创建一个更小的片段。初始片段覆盖索引 1 到 4(元素 2、3、4、5)。子片段从片段的索引 1 开始切片 2 个元素(元素 3、4)。Slice 方法是高效的,因为它创建了一个新的 ArraySegment 而不复制数据,从而保持对原始数组的引用。ToArray 方法用于显示,但切片本身避免了分配。

修改 ArraySegment 元素

ArraySegment 允许修改底层数组。此示例演示了通过片段更新元素。

Program.cs
int[] numbers = [1, 2, 3, 4, 5];
ArraySegment<int> segment = new(numbers, 2, 2);

segment[0] = 10;
segment[1] = 20;

Console.WriteLine($"Modified array: {string.Join(", ", numbers)}");

该程序创建一个 ArraySegment,覆盖索引 2 和 3(元素 3、4),并修改其元素。将新值分配给 segment[0]segment[1] 会将原始数组中这些索引处的元素更新为 10 和 20。由于 ArraySegment 是原始数组的视图,因此更改会立即反映出来。

这演示了 ArraySegment 用于就地修改的效率,避免了创建新数组或复制数据的需要,同时保持对底层内存的直接访问。

将 ArraySegment 与缓冲区一起使用

ArraySegment 对于缓冲区管理非常有用。此示例将字节缓冲区处理为一个片段。

Program.cs
byte[] buffer = [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00];
ArraySegment segment = new(buffer, 0, 4);

int value = BitConverter.ToInt32(segment);
Console.WriteLine($"Integer from buffer: {value}");

该示例使用一个 ArraySegment 从字节缓冲区读取一个整数。该片段覆盖缓冲区的前 4 个字节,这些字节以小端格式表示一个 32 位整数。BitConverter.ToInt32 直接将片段转换为一个整数,利用 ArraySegment 提供缓冲区视图而无需复制的能力。这在网络编程或文件解析等场景中特别有用,在这些场景中,需要高效地处理缓冲区,而无需为每个操作分配新内存。

迭代 ArraySegment

ArraySegment 支持迭代其元素。此示例迭代片段的元素。

Program.cs
int[] numbers = [1, 2, 3, 4, 5];
ArraySegment<int> segment = new(numbers, 1, 3);
int sum = 0;

foreach (int num in segment)
{
    sum += num;
}

Console.WriteLine($"Sum of segment: {sum}");

该程序创建一个 ArraySegment,覆盖索引 1 到 3(元素 2、3、4),并使用 foreach 循环对其元素求和。ArraySegment 实现了 IEnumerable,允许像集合一样迭代其元素。该循环仅访问片段的元素,而不是整个数组,从而确保效率。此方法对于处理大型数组的特定部分非常有用,例如在计算聚合或过滤数据时,而无需复制或重新分配内存。

将 ArraySegment 传递给方法

ArraySegment 对于将数组部分传递给方法非常有用。此示例演示了方法用法。

Program.cs
int[] numbers = [1, 2, 3, 4, 5];
ArraySegment<int> segment = new(numbers, 1, 3);

Console.WriteLine($"Segment max: {FindMax(segment)}");

static int FindMax(ArraySegment<int> segment)
{
    int max = segment[0];
    foreach (int num in segment)
    {
        if (num > max) max = num;
    }
    return max;
}

该示例将一个 ArraySegment 传递给一个查找最大值的方法。该片段覆盖索引 1 到 3(元素 2、3、4)。FindMax 方法迭代该片段以确定最大值,将其视为一个集合。

传递一个 ArraySegment 而不是复制一个新数组或传递带有索引的整个数组更有效,因为它封装了偏移量和计数,同时保持对原始数组数据的直接访问。这非常适合处理数组子集的模块化代码。

带有字符串的 ArraySegment

ArraySegment 可以与字符数组一起使用,用于类似字符串的操作。此示例处理字符串的字符。

Program.cs
string text = "Hello, World!";
char[] chars = text.ToCharArray();
ArraySegment segment = new(chars, 0, 5);

Console.WriteLine($"Segment as string: {new string(segment.ToArray())}");

该程序将一个字符串转换为字符数组,并创建一个覆盖前 5 个字符 ("Hello") 的 ArraySegment。使用 new string(segment.ToArray()) 将该片段转换回字符串以进行显示。

虽然 ArraySegment 主要设计用于数组,但此示例通过将字符串视为字符数组来展示其在字符串处理中的适用性。虽然通常首选 ReadOnlySpan 用于字符串操作,因为它本质上是无分配的,但在处理遗留代码或需要修改底层数组时,ArraySegment 非常有用,因为它提供了一个可变的视图。

来源

ArraySegment 结构文档

本教程介绍了如何在 C# 中使用 ArraySegment 进行高效的数组切片和操作,包括访问、修改和迭代数组片段。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 C# 教程