C#中的Span和Memory是.NET平台为了优化内存操作效率而推出的两种核心类型,它们能够在不需要额外内存分配的前提下,提供对连续内存区域的安全访问能力,广泛应用于字符串处理、数组操作、网络数据解析等性能敏感场景。

Span和Memory的基本概念
Span<T>是一个值类型,它表示一个连续内存区域的视图,可以指向栈内存、堆内存或者非托管内存,它的生命周期受限于定义它的栈帧,因此不能在异步方法、迭代器或者闭包中使用。
Memory<T>是一个引用类型,同样是连续内存区域的视图,但是它不受栈生命周期限制,支持在异步方法、迭代器等场景中使用,适合需要跨方法传递内存视图的场景。
核心特性对比
两者的核心差异主要体现在生命周期和使用限制上,具体对比如下:
| 特性 | Span<T> | Memory<T> |
|---|---|---|
| 类型归属 | 值类型 | 引用类型 |
| 生命周期限制 | 受栈帧限制,不能逃逸出当前栈 | 无栈帧限制,可跨方法传递 |
| 异步场景支持 | 不支持 | 支持 |
| 性能表现 | 更高,无堆分配开销 | 略低,存在引用类型分配开销 |
Span的使用场景和示例
Span适合在方法内部进行临时内存操作,比如字符串切片、数组分段处理等场景,避免不必要的字符串或数组拷贝。
字符串切片示例
传统的字符串Substring方法会生成新的字符串对象,而使用Span可以避免这个分配开销:
using System;
class Program
{
static void Main()
{
string source = "HelloWorldCSharp";
// 使用Span获取从索引5开始长度为5的字符视图,无新字符串分配
ReadOnlySpan<char> span = source.AsSpan(5, 5);
// 将Span转换为字符串输出,仅演示用,实际场景可直接操作Span
Console.WriteLine(new string(span)); // 输出World
}
}
数组分段处理示例
处理数组的部分元素时,使用Span可以避免创建新的数组:
using System;
class Program
{
static void ProcessPartialArray(int[] array)
{
// 获取数组前3个元素的Span视图
Span<int> partialSpan = array.AsSpan(0, 3);
for (int i = 0; i < partialSpan.Length; i++)
{
partialSpan[i] *= 2; // 直接修改原数组对应元素
}
}
static void Main()
{
int[] arr = { 1, 2, 3, 4, 5 };
ProcessPartialArray(arr);
Console.WriteLine(string.Join(",", arr)); // 输出2,4,6,4,5
}
}
Memory的使用场景和示例
Memory适合需要跨方法、跨异步方法传递内存视图的场景,比如异步读取数据后处理数据。
异步场景使用示例
以下示例模拟异步读取数据后,使用Memory传递数据视图进行处理:
using System;
using System.Threading.Tasks;
class Program
{
// 异步方法返回Memory视图,可安全跨方法传递
static async Task<Memory<byte>> ReadDataAsync()
{
byte[] buffer = new byte[1024];
// 模拟异步读取操作
await Task.Delay(100);
// 填充模拟数据
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)(i % 256);
}
return buffer.AsMemory();
}
static void ProcessData(Memory<byte> data)
{
// 转换为Span进行处理,Span仅在此方法内部使用
Span<byte> span = data.Span;
Console.WriteLine($"数据长度:{span.Length},第一个字节:{span[0]}");
}
static async Task Main()
{
Memory<byte> dataMemory = await ReadDataAsync();
ProcessData(dataMemory);
}
}
使用注意事项
- Span不能存储在字段中,也不能作为异步方法的返回值,否则会出现生命周期不匹配的问题
- Memory转换为Span时需要注意,Span的生命周期不能超过对应的Memory的生命周期
- 操作Span和Memory时不需要担心越界问题,访问超出范围的元素会直接抛出IndexOutOfRangeException
- 尽量避免不必要的Span和Memory转换,减少额外的性能开销
总结
Span和Memory都是C#中用于高性能内存操作的重要类型,Span适合栈内的临时内存访问,性能更高;Memory适合需要跨栈传递的内存访问场景,兼容性更好。开发者可以根据具体的使用场景选择合适的类型,在需要频繁操作字符串、数组等连续内存区域时,合理使用这两种类型可以有效减少内存分配,提升应用的运行效率。