C# 试题 11:结构体与 ref struct
0201 以下代码输出什么?
难度: 实战
struct Point { public int X; }
var a = new Point { X = 1 };
var b = a;
b.X = 2;
Console.WriteLine($"{a.X},{b.X}");
- A.
1,1 - B.
1,2,结构体赋值逐字段复制,b是独立副本 - C.
2,2 - D. 编译失败,结构体没有复制语义
查看答案与解析
正确答案B
解析: 结构体赋值复制全部字段;修改 b.X 不影响 a。这与引用类型赋值产生“别名”的行为正好相反。
关键点: 结构体赋值 = 复制,不是建立引用。
0202 以下代码输出什么?
难度: 实战
struct Point { public int X; }
static void Move(Point p) => p.X = 99;
var p = new Point { X = 1 };
Move(p);
Console.WriteLine(p.X);
- A. 输出
1,按值传参会复制,方法内修改不影响实参 - B. 输出
99 - C. 编译失败,结构体不能作为参数
- D. 输出
0
查看答案与解析
正确答案A
解析: 默认按值传递,Move 修改的是副本。想让修改回流,必须用 ref(或可变语义的 in 之外的其他方案)。
关键点: 值类型参数默认复制,修改不回流。
0203 以下代码输出什么?
难度: 进阶
struct Counter { public int Value; public void Increment() => Value++; }
static void Take(in Counter c) => c.Increment();
var c = new Counter { Value = 0 };
Take(c);
Console.WriteLine(c.Value);
- A. 输出
1 - B. 编译失败,不能对
in参数调用实例方法 - C. 输出
0,in参数是只读引用,调用可变方法前编译器先做防御性复制 - D. 运行抛
InvalidOperationException
查看答案与解析
正确答案C
解析: Increment 不是 readonly 成员,编译器在只读的 in 引用上先复制一份再调用,修改落在副本上,原值不变。这也意味着“用 in 省复制”的前提是成员都只读。
关键点: in 传参遇到可变成员会触发防御性复制。
0204 以下代码输出什么?
难度: 实战
struct Point { public int X; }
static void Move(ref Point p) => p.X = 99;
var p = new Point { X = 1 };
Move(ref p);
Console.WriteLine(p.X);
- A. 输出
1 - B. 编译失败,结构体不能用
ref传递 - C. 输出
0 - D. 输出
99,ref传递的是实参的存储位置
查看答案与解析
正确答案D
解析: ref 参数共享实参的存储位置,方法内对字段的修改直接作用于原变量,没有复制。
关键点: ref 让值类型也具备“别名”语义。
0205 以下代码能否编译?
难度: 进阶
readonly struct S
{
public int X;
}
- A. 编译通过,
X自动只读 - B. 编译失败,readonly 结构体的实例字段必须声明为 readonly
- C. 编译通过,但运行抛异常
- D. 编译失败,readonly 结构体不能有字段
查看答案与解析
正确答案B
解析: readonly struct 要求所有实例字段都带 readonly(CS8340),这是结构性约束,确保实例天然不可变。
关键点: readonly 是结构层面的强制,不只是文档约定。
0206 以下代码能否编译?
难度: 进阶
readonly struct S
{
public readonly int X;
public void Bad() => X = 1;
}
- A. 编译失败,readonly 结构体的实例方法不能给字段赋值
- B. 编译通过,赋值只在副本上发生
- C. 运行抛
FieldAccessException - D. 编译通过,
X变为1
查看答案与解析
正确答案A
解析: readonly 结构体的实例成员运行在 readonly 上下文中,写入字段是编译错误。想要“可写”只能靠构造或静态成员。
关键点: readonly 结构体禁止实例级写操作。
0207 以下代码能否编译?
难度: 实战
ref struct R { public int X; }
var r = new R { X = 1 };
object o = r;
- A. 编译通过,发生装箱
- B. 编译通过,不发生装箱
- C. 编译失败,ref struct 不能被装箱(不能转换为
object或接口) - D. 运行抛
InvalidCastException
查看答案与解析
正确答案C
解析: 装箱会把值移到托管堆上,ref struct 被禁止进入堆,因此到 object/接口的任何转换都是编译错误。
关键点: ref struct 生命周期约束的起点就是“不能装箱”。
0208 以下代码能否编译?
难度: 实战
ref struct R { }
class C
{
R _r;
}
- A. 编译通过,字段默认在栈上
- B. 编译通过,但运行抛异常
- C. 编译通过,自动转为引用类型
- D. 编译失败,ref struct 不能作为非 ref struct 的实例字段
查看答案与解析
正确答案D
解析: 类实例分配在堆上,ref struct 字段会被搬进堆,违反栈约束;只有 ref struct 内部才能包含 ref struct 实例字段。
关键点: ref struct 只能活在栈上或 ref struct 内部。
0209 以下代码能否编译?
难度: 进阶
ref struct R { public int X; }
static Func<int> Make()
{
R r = default;
return () => r.X;
}
- A. 编译通过
- B. 编译失败,闭包会把
r提升到堆上,ref struct 不能被捕获 - C. 编译通过,但运行抛异常
- D. 编译通过,返回
0
查看答案与解析
正确答案B
解析: lambda/局部函数捕获局部变量时,编译器会把它提升到堆分配的环境对象中;ref struct 不能进入堆,因此捕获是编译错误。
关键点: 任何“逃逸到堆”的路径对 ref struct 都是禁区。
0210 关于 ref struct 的生命周期约束,哪项正确?
难度: 进阶
- A. ref struct 可以存储在 async 方法的局部变量中并跨越 await
- B. ref struct 可以被装箱为
object - C. ref struct 不能装箱、不能作类字段、不能被闭包捕获、不能跨越 await,只能存活于栈帧或 ref struct 内部
- D. ref struct 可以继承普通类
查看答案与解析
正确答案C
解析: ref struct 的所有限制都源于“不能进入托管堆”:装箱、类字段、闭包提升、async/iterator 的挂起存储都会把它搬到堆上,故全部禁止;结构体本就不能继承类。
关键点: 理解“堆 = 禁区”,就能推出全部限制。
0211 以下代码输出什么?
难度: 实战
record struct Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
Console.WriteLine(p1 == p2);
- A. 输出
True,record struct 按成员值比较并生成== - B. 输出
False,结构体默认引用比较 - C. 编译失败,结构体不能使用
== - D. 输出
False,record struct 不做相等比较
查看答案与解析
正确答案A
解析: record struct 会合成基于成员的 Equals、GetHashCode 与 ==/!=;普通 struct 没有这个待遇。
关键点: record 家族自动获得值相等,普通 struct 不会。
0212 以下代码输出什么?
难度: 进阶
record struct Point(int X, int Y);
var p = new Point(1, 2);
p.X = 100;
Console.WriteLine(p.X);
- A. 编译失败,位置参数生成的属性只读
- B. 编译失败,record struct 不可变
- C. 输出
1 - D. 输出
100,位置 record struct 的属性是get; set;,默认可变
查看答案与解析
正确答案D
解析: 位置 record struct 合成的是可写属性;只有 readonly record struct 才生成 init 只读属性。这与 record class 的不可变语义不同。
关键点: record struct 默认可变,别拿 record class 的印象套它。
0213 以下代码能否编译?
难度: 进阶
readonly record struct Point(int X, int Y);
var p = new Point(1, 2);
p.X = 100;
- A. 编译通过,输出
100 - B. 编译失败,readonly record struct 的位置属性是
init,只能在初始化时赋值 - C. 编译通过,赋值被忽略
- D. 运行抛
InvalidOperationException
查看答案与解析
正确答案B
解析: readonly record struct 合成只读 init 属性,构造完成后赋值是编译错误。加不加 readonly 决定了 record struct 的可变性。
关键点: readonly 前缀改变 record struct 的默认可变性。
0214 以下代码输出什么?
难度: 实战
record struct Point(int X, int Y);
var p = new Point(1, 2);
var q = p with { X = 9 };
Console.WriteLine($"{p.X},{q.X}");
- A. 输出
1,9,with生成副本,原值不变 - B. 输出
9,9 - C. 编译失败,record struct 不支持
with - D. 输出
1,1
查看答案与解析
正确答案A
解析: with 表达式复制当前实例并应用修改:p 保持不变,q 是新副本。值语义下“复制不破坏源值”尤其直观。
关键点: with 不修改源值,始终产出新实例。
0215 大结构体传参的性能选型,哪项正确?
难度: 进阶
- A. 值传递永远比
in快 - B. 只要声明了
in,就一定零复制 - C. 值参数会复制整个结构体;
in/ref readonly传引用可避免复制,但访问非 readonly 成员会触发防御性复制,收益需基准验证 - D.
in参数允许方法内修改实参
查看答案与解析
正确答案C
解析: in 是只读引用,不复制存储;但任何可能修改的访问(调用可变方法、访问带 setter 的属性)都会引入防御性复制。收益依赖调用模式与 JIT,必须实测。
关键点: 大结构体该不该用 in,先基准再下结论。
0216 以下代码能否编译?
难度: 实战
struct S { public int X; }
static void Bad(in S s) => s.X = 5;
- A. 编译通过,修改被忽略
- B. 编译通过,修改作用于实参
- C. 编译通过,在副本上修改
- D. 编译失败,
in参数是只读引用,不能直接赋值
查看答案与解析
正确答案D
解析: in 参数按只读引用传递,直接写入字段/属性是编译错误。这与“调用可变方法产生防御性复制”不同:显式赋值连复制都不会做,直接编译期拒绝。
关键点: in = 只读引用,写操作在编译期被禁止。
0217 以下代码能否编译?
难度: 进阶
struct Point { public int X; }
var a = new Point { X = 1 };
var b = new Point { X = 1 };
Console.WriteLine(a == b);
- A. 输出
True - B. 编译失败,普通结构体不会自动生成
==,需自行重载运算符 - C. 输出
False - D. 运行抛
NotSupportedException
查看答案与解析
正确答案B
解析: 自定义 struct 不会像 record struct 那样合成 ==;未重载运算符时 == 不适用,编译失败。很多资深开发者会在这里踩坑。
关键点: 普通 struct 的“值相等”需要显式实现,不是默认行为。
0218 以下代码输出什么?
难度: 进阶
struct S
{
public int X;
public S() { X = 42; }
}
S a = new S();
S b = default;
Console.WriteLine($"{a.X},{b.X}");
- A. 输出
42,0,new S()调用显式无参构造函数,default跳过构造函数直接零初始化 - B. 输出
42,42 - C. 输出
0,0 - D. 编译失败,结构体不能声明无参构造函数
查看答案与解析
正确答案A
解析: C# 10 起结构体可声明显式无参构造函数,new S() 会执行它;但 default(S) 不经过任何构造函数,字段全为零。二者语义必须区分。
关键点: default 永远绕开构造函数,这是结构体与类的关键差异。
0219 以下代码能否编译?
难度: 进阶
ref struct R { }
var list = new List<R>();
- A. 编译通过,ref struct 是合法的泛型实参
- B. 编译通过,但运行抛异常
- C. 编译失败,
List<T>未声明allows ref struct,ref struct 不能作为其类型实参 - D. 编译通过,发生装箱
查看答案与解析
正确答案C
解析: C# 13 起 ref struct 只能用于带 allows ref struct 反约束的类型参数;List<T> 没有该标注,故编译失败。泛型支持 ref struct 是有条件的。
关键点: 泛型默认拒绝 ref struct,除非显式放行。
0220 大结构体使用 in 参数的正确姿势,哪项最准确?
难度: 进阶
- A. 所有结构体都应默认用
in,收益必然为正 - B.
in参数在方法内可以任意修改 - C.
in与值参数在语义上完全相同 - D. 对“大且只读”的结构体评估使用
in/ref readonly,留意非 readonly 成员的防御性复制,并用基准验证收益
查看答案与解析
正确答案D
解析: in 适合“大而只读”的结构体;小结构体或访问可变成员时,防御性复制可能抵消收益。选型必须结合调用点与基准数据。
关键点: in 是优化手段而非规则,一切以测量为准。