C# 试题 21:Span 与 Memory
0401 以下代码输出什么?
难度: 实战
int[] a = { 1, 2, 3 };
Span<int> s = a;
s[0] = 42;
Console.WriteLine(a[0]);
- A.
1 - B.
42,Span是数组元素的共享视图 - C. 编译失败,
Span不能指向托管数组 - D. 运行抛
IndexOutOfRangeException
查看答案与解析
正确答案B
解析: Span<T> 由数组构造时直接引用底层存储,写入 s[0] 等价于写入 a[0],与数组切片是拷贝语义完全不同。
关键点: Span 是视图不是副本,读写都会穿透到被引用的存储。
0402 以下代码输出什么?
难度: 实战
int[] a = { 1, 2, 3, 4, 5 };
Span<int> s = a.AsSpan(1, 3);
s[0] = 99;
Console.WriteLine(string.Join(",", a));
- A.
1,2,3,4,5 - B.
1,2,99,4,5 - C.
99,2,3,4,5 - D.
1,99,3,4,5
查看答案与解析
正确答案D
解析: AsSpan(1, 3) 创建指向 a[1..4] 的视图,s[0] 即 a[1],所以改为 99 后数组变成 1,99,3,4,5。
关键点: Span 切片零拷贝、共享存储,修改视图会反映到原数组。
0403 以下代码输出什么?
难度: 实战
ReadOnlySpan<char> s = "hello";
Console.WriteLine(s.Length);
Console.WriteLine(s[0]);
- A.
5和h - B. 编译失败,
string不能隐式转换为ReadOnlySpan<char> - C.
5和0 - D. 运行抛
NullReferenceException
查看答案与解析
正确答案A
解析: C# 11 起 string 可隐式转换为 ReadOnlySpan<char>,视图直接引用字符串内部存储,s[0] 为 h。
关键点: 字符串只能得到只读视图,不能得到可写的 Span<char>。
0404 以下代码能否编译?
难度: 进阶
ReadOnlySpan<int> s = new int[] { 1, 2, 3 };
s[0] = 9;
- A. 编译通过,写入成功
- B. 运行抛
NotSupportedException - C. 编译失败,
ReadOnlySpan<T>的索引器只有 getter - D. 运行抛
InvalidOperationException
查看答案与解析
正确答案C
解析: ReadOnlySpan<T> 的索引器是只读的,s[0] = 9 在编译期就报错;它不是运行期拒绝,而是类型系统层面的约束。
关键点: 只读性是编译期强制,不是运行期异常。
0405 以下代码输出什么?
难度: 实战
Span<int> s = stackalloc int[4];
s[0] = 7;
Console.WriteLine(s[0] + " " + s.Length);
- A.
0 4 - B.
7 4 - C. 编译失败,stackalloc 必须写在
unsafe中 - D. 运行抛
StackOverflowException
查看答案与解析
正确答案B
解析: 把 stackalloc 结果赋给 Span<T> 是受支持的安全写法,不需要 unsafe;内存分配在线程栈上,写入 s[0] 得到 7,长度为 4。
关键点: Span<T> = stackalloc ... 是安全代码,不等于指针。
0406 以下代码能否编译?
难度: 进阶
async Task M()
{
Span<int> s = stackalloc int[4];
await Task.Delay(1);
Console.WriteLine(s[0]);
}
- A. 编译通过并正常运行
- B. 运行抛
InvalidOperationException - C. 编译失败,stackalloc 需要
unsafe - D. 编译失败,
Span是 ref struct,不能跨await保留
查看答案与解析
正确答案D
解析: async 方法的状态机要保存局部变量,而 ref struct 不能被存入状态机;即使代码里只有一次 await,编译器也会直接报错(CS4007)。
关键点: “没用 await 跨过它”也不行,async 方法里根本不能声明 ref struct 局部变量。
0407 以下代码输出什么?
难度: 实战
async Task M()
{
var arr = new int[10];
Memory<int> m = arr;
await Task.Delay(1);
m.Span[0] = 42;
Console.WriteLine(arr[0]);
}
await M();
- A. 输出
42,Memory<T>可以跨await传递 - B. 编译失败
- C. 输出
0 - D. 运行抛
NullReferenceException
查看答案与解析
正确答案A
解析: Memory<T> 是普通结构,可以存进 async 状态机、跨 await 传递;await 之后再取 .Span 使用是合法的,写入会反映到原数组。
关键点: Span 不能跨 await,Memory 可以;需要“取 Span”的时机是同步使用点。
0408 以下代码能否编译?
难度: 进阶
class Buffer
{
private Span<int> _data;
}
- A. 编译通过,
Span可以作为普通类字段 - B. 编译通过,但运行抛异常
- C. 编译失败,ref struct 不能作为普通类的字段
- D. 编译失败,字段名不能以下划线开头
查看答案与解析
正确答案C
解析: ref struct 的实例可能指向栈内存,因此不能作为普通类/结构的字段;只有 ref struct 的实例成员才能持有 Span<T> 字段(CS8345)。
关键点: “Span 不能存字段”只针对普通类型,ref struct 内部可以。
0409 以下代码能否编译?
难度: 进阶
Span<int> s = new int[3];
object o = s;
- A. 编译通过并完成装箱
- B. 编译失败,ref struct 不能被装箱
- C. 运行抛
InvalidOperationException - D. 编译通过,
o得到数组副本
查看答案与解析
正确答案B
解析: 装箱要求把值复制到堆上的对象里,ref struct 生命周期受限、不能上堆,所以任何“转 object/接口”的装箱尝试都在编译期失败。
关键点: ref struct 不能装箱是硬性规则,Span 尤其如此。
0410 以下代码输出什么?
难度: 进阶
int[] a = { 1, 2, 3, 4, 5 };
a.AsSpan(0, 4).CopyTo(a.AsSpan(1, 4));
Console.WriteLine(string.Join(",", a));
- A.
1,1,1,1,1 - B.
1,2,3,4,5 - C.
2,3,4,5,5 - D.
1,1,2,3,4
查看答案与解析
正确答案D
解析: MemoryExtensions.CopyTo 对重叠区间按“先拷贝到临时位置”的语义处理(类似 memmove):源 1,2,3,4 完整拷到目标后,数组为 1,1,2,3,4。
关键点: CopyTo 重叠安全;手写前向循环复制才会被污染。
0411 以下代码输出什么?
难度: 实战
int[] a = { 1, 2, 3, 4, 5 };
for (int i = 0; i < 4; i++)
{
a[i + 1] = a[i];
}
Console.WriteLine(string.Join(",", a));
- A.
1,1,1,1,1,前向复制被已覆盖的值污染 - B.
1,1,2,3,4 - C.
2,3,4,5,5 - D.
1,2,3,4,5
查看答案与解析
正确答案A
解析: 前向循环里 a[1] 先被改成 1,随后 a[2] = a[1] 读到的已是新值 1,依次传播,最终全部变成 1;这就是重叠复制要用 CopyTo/Array.Copy 的原因。
关键点: 重叠复制必须用库函数或反向循环,手写前向循环是经典 bug。
0412 以下代码能否编译?
难度: 进阶
var list = new List<Span<int>>();
- A. 编译通过
- B. 运行抛
TypeInitializationException - C. 编译失败,ref struct 不能作为泛型类型参数
- D. 编译通过,但只能存 8 个元素
查看答案与解析
正确答案C
解析: ref struct 不能作为泛型类型实参(CS9244),因此 List<Span<int>>、Dictionary<..., Span<T>> 等都无法编译;跨泛型传递需用 Memory<T>。
关键点: 需要“集合存视图”时改用 Memory<T>,它是普通结构。
0413 以下代码能否编译?
难度: 实战
string text = "abc";
Span<char> s = text.AsSpan();
- A. 编译通过,获得可写视图
- B. 运行抛
InvalidOperationException - C. 运行抛
NotSupportedException - D. 编译失败,
string只能得到ReadOnlySpan<char>,不能隐式转Span<char>
查看答案与解析
正确答案D
解析: string.AsSpan() 返回 ReadOnlySpan<char>,只读视图不能赋给可写的 Span<char>(CS0029)。想写字符缓冲得自己分配数组或 StringBuilder。
关键点: 字符串内部存储不可写,类型系统保证这一点。
0414 以下代码能否编译?
难度: 进阶
Span<string> s = stackalloc string[3];
- A. 编译失败,stackalloc 只能用于非托管类型,
string是托管引用类型 - B. 编译通过,
string也可以 stackalloc - C. 运行抛
OutOfMemoryException - D. 编译失败,stackalloc 必须写在
unsafe中
查看答案与解析
正确答案A
解析: stackalloc 只允许非托管类型(CS0208);栈上不能存放引用类型,因此 stackalloc string[...]、Span<object> 这类写法无法编译。
关键点: “stackalloc 返回 Span”成立的前提是元素类型非托管。
0415 以下代码输出什么?
难度: 实战
Span<int> s = stackalloc int[] { 1, 2, 3 };
Console.WriteLine(s[2]);
- A.
0 - B.
3 - C. 编译失败
- D. 运行抛
IndexOutOfRangeException
查看答案与解析
正确答案B
解析: stackalloc 支持集合初始化器写法,s 指向栈上的 {1,2,3},s[2] 是 3。
关键点: 初始化列表与长度两种 stackalloc 写法都返回 Span<T>。
0416 以下代码的问题在哪?
难度: 实战
byte[] arr = ArrayPool<byte>.Shared.Rent(100);
Memory<byte> m = arr;
ArrayPool<byte>.Shared.Return(arr);
m.Span[0] = 1; // 归还后继续写
- A. 没有问题,
Memory<T>是安全拷贝 - B. 编译失败,
Memory<byte>不能由数组构造 - C. 归还后
m仍指向原数组,池可能已把它复用于其他请求,继续写会破坏池中数据 - D. 运行抛
ObjectDisposedException
查看答案与解析
正确答案C
解析: Memory<T> 只是数组的引用,不会阻止归还;Return 之后通过 m 继续写入就是“归还后继续使用”,属于未定义行为,可能污染其他租借者的数据。这里不会自动抛异常,正因如此更危险。
关键点: 生命周期责任在调用方:归还前完成所有读写,之后不要再持有引用。
0417 同步方法里只需只读遍历一段字符串,不跨 await、不存字段,应优先选择哪种类型?
难度: 进阶
- A.
Memory<char>,可跨 await - B.
string,直接下标最省事 - C.
ReadOnlyMemory<char>,一定不分配 - D.
ReadOnlySpan<char>,同步场景零开销、无额外分配
查看答案与解析
正确答案D
解析: 同步、只读、不逃逸的场景正是 ReadOnlySpan<char> 的目标用途:不分配、不拷贝、直接引用原存储;Memory 是为跨 async/存字段设计的。
关键点: 选型口诀:同步用 Span,异步/字段用 Memory,只读加 ReadOnly 前缀。
0418 以下代码输出什么?
难度: 进阶
ReadOnlySpan<int> s = stackalloc int[] { 5, 6, 7 };
Console.WriteLine(s[0]);
- A.
5 - B.
0 - C. 编译失败,
ReadOnlySpan不能由 stackalloc 构造 - D. 运行抛异常
查看答案与解析
正确答案A
解析: Span<T> 可以隐式转换为 ReadOnlySpan<T>,所以 stackalloc 结果赋给只读视图同样合法;s[0] 是 5,且视图只读。
关键点: Span → ReadOnlySpan 是隐式转换,反向不行。
0419 关于 Span<T> 与 Memory<T> 的本质区别,哪项正确?
难度: 进阶
- A.
Span<T>分配在堆上,Memory<T>分配在栈上 - B.
Span<T>是 ref struct,只能存在于栈上、不能装箱或存字段;Memory<T>是普通结构,可存字段、可跨 async - C. 两者都不能指向托管数组
- D.
Memory<T>的性能总是优于Span<T>
查看答案与解析
正确答案B
解析: Span<T> 是 ref struct,受限最多但开销最低;Memory<T> 是普通结构,能被装箱、存字段、跨 async,代价是访问元素前要先取 .Span。
关键点: 约束不同源于“是否可能逃逸到堆上”,而不是功能强弱。
0420 以下代码输出什么?
难度: 实战
ref struct Buffer
{
public Span<int> Data;
}
var b = new Buffer { Data = new int[3] };
b.Data[0] = 1;
Console.WriteLine(b.Data[0]);
- A. 编译失败,ref struct 不能包含
Span字段 - B. 运行抛
NullReferenceException - C. 输出
1,ref struct 可以包含Span<T>字段 - D. 输出
0
查看答案与解析
正确答案C
解析: ref struct 的实例成员可以持有 Span<T> 字段,只要整个类型不逃逸到堆上;本例编译运行正常,输出 1。
关键点: “Span 不能做字段”仅限普通 class/struct,ref struct 内部是允许的。