C# 试题 12:record、init 与 required
0221 以下代码输出什么?
难度: 实战
record Point(int X, int Y);
class CPoint
{
public int X { get; }
public int Y { get; }
public CPoint(int x, int y) { X = x; Y = y; }
}
var r1 = new Point(1, 2);
var r2 = new Point(1, 2);
var c1 = new CPoint(1, 2);
var c2 = new CPoint(1, 2);
Console.WriteLine(r1 == r2);
Console.WriteLine(c1 == c2);
- A.
True和False,record 默认值相等,普通 class 默认引用相等 - B.
True和True - C.
False和False - D.
False和True
查看答案与解析
正确答案A
解析: record 合成基于成员的 Equals 与 ==;普通 class 未重载时 == 比较引用。这是 record 最核心的卖点,也是最容易记反的点。
关键点: record 带来“值相等”,class 默认“引用相等”。
0222 以下代码输出什么?
难度: 进阶
record Bag(int[] Items);
var a = new Bag(new[] { 1, 2 });
var b = new Bag(new[] { 1, 2 });
Console.WriteLine(a == b);
- A. 输出
True - B. 输出
True,数组被自动展开比较 - C. 输出
False,record 相等性是浅比较,数组成员按引用比较 - D. 编译失败,record 不能包含数组
查看答案与解析
正确答案C
解析: record 的 Equals 逐成员调用成员的 Equals;数组没有值相等语义,比较的是引用,内容相同也返回 false。record 不是深比较。
关键点: record 相等性是“成员级”浅比较,不是递归深比较。
0223 以下代码能否编译?
难度: 实战
record Point(int X, int Y);
var p = new Point(1, 2);
p.X = 9;
- A. 编译通过,输出
9 - B. 编译失败,位置 record class 的属性是
init,构造完成后不可赋值 - C. 编译通过,赋值被忽略
- D. 运行抛
InvalidOperationException
查看答案与解析
正确答案B
解析: 位置 record class 合成 get; init; 属性;想“改”只能通过 with 生成新实例。
关键点: record class 不可变,record struct 可变,别混淆。
0224 以下代码输出什么?
难度: 进阶
record Base(int X);
record Derived(int X, int Y) : Base(X);
Base b = new Derived(1, 2);
Base c = b with { X = 5 };
Console.WriteLine(c.GetType().Name);
- A. 输出
Base - B. 编译失败,基类引用不能用
with - C. 输出
Object - D. 输出
Derived,with保持运行时类型,通过复制构造函数重建
查看答案与解析
正确答案D
解析: with 调用复制构造函数,复制构造函数保留派生类型;即使静态类型是 Base,结果运行时类型仍是 Derived。
关键点: with 是“同类型复制 + 修改”,不会退化成基类。
0225 以下代码输出什么?
难度: 实战
record R(int[] Items);
var a = new R(new[] { 1, 2 });
var b = a with { };
Console.WriteLine(ReferenceEquals(a.Items, b.Items));
- A. 输出
True,with是浅拷贝,引用成员仍然共享 - B. 输出
False,with深拷贝所有成员 - C. 编译失败
- D. 输出
False,数组被复制
查看答案与解析
正确答案A
解析: with 只复制成员引用本身,不复制引用指向的对象;Items 数组在 a、b 之间共享。修改数组内容会同时影响两个记录。
关键点: 不可变 record + 可变引用成员 = 可被观察到的“共享突变”。
0226 以下代码能否编译?
难度: 实战
class C
{
public int X { get; init; }
}
var c = new C { X = 1 };
c.X = 2;
- A. 编译通过,输出
2 - B. 编译通过,输出
1 - C. 编译失败,init 属性只能在对象初始化阶段赋值
- D. 运行抛
InvalidOperationException
查看答案与解析
正确答案C
解析: init-only 的赋值窗口是对象初始化器、with 表达式与(含派生类的)构造函数;构造完成后任何赋值都是编译错误。
关键点: init 是“编译期窗口”,不是运行时校验。
0227 以下代码输出什么?
难度: 进阶
class C
{
public int X { get; init; }
public C(int x) { X = x; }
}
Console.WriteLine(new C(5).X);
- A. 编译失败,构造函数不能给 init 属性赋值
- B. 输出
5,声明类型的构造函数可以给 init 属性赋值 - C. 输出
0 - D. 编译失败,init 属性必须有 setter
查看答案与解析
正确答案B
解析: init 访问器可在本类型构造函数(以及派生类构造函数)中赋值,这是官方允许的初始化路径之一。
关键点: 构造函数是 init 的合法赋值点。
0228 以下代码能否编译?
难度: 实战
class User
{
public required string Name { get; init; }
}
var u = new User();
- A. 编译通过,
Name为null - B. 编译通过,
Name为空串 - C. 运行抛
MissingMemberException - D. 编译失败,required 成员必须通过对象初始化器或带
[SetsRequiredMembers]的构造函数赋值
查看答案与解析
正确答案D
解析: required 是编译期契约:调用方未设置 required 成员即报编译错误。它把“忘记初始化”从运行时提前到编译期。
关键点: required 让“漏初始化”无法编译通过。
0229 以下代码输出什么?
难度: 进阶
using System.Diagnostics.CodeAnalysis;
class User
{
public required string Name { get; init; }
[SetsRequiredMembers]
public User() { }
}
var u = new User();
Console.WriteLine(u.Name is null);
- A. 输出
True,[SetsRequiredMembers]声明“构造函数已设置全部 required 成员”,编译器不再校验,但构造函数实际没赋值 - B. 输出
False - C. 编译失败
- D. 运行抛
NullReferenceException
查看答案与解析
正确答案A
解析: [SetsRequiredMembers] 是给编译器的承诺而非检查;构造函数未真正赋值时,调用方仍能编译通过,Name 在运行时是 null。这把编译期保证重新变成了运行时风险。
关键点: required 的可信度取决于构造函数是否诚实实现。
0230 关于 required 的语义,哪项正确?
难度: 进阶
- A. required 只允许用在
init属性上 - B. required 会改变类型的运行时内存布局
- C. required 是编译期契约:要求调用方通过对象初始化器(或
[SetsRequiredMembers]构造函数)提供值;反射、序列化等运行时路径不会自动获得该校验 - D. required 成员在
new时自动填充默认值
查看答案与解析
正确答案C
解析: required 通过编译器强制调用方初始化;它不改变 IL 布局,也没有运行时拦截。依赖反射/反序列化创建对象时,仍需自行校验。
关键点: required 的“强制”止步于编译期。
0231 以下代码输出什么?
难度: 实战
record Base(int X);
record Derived(int X, int Y) : Base(X);
var d1 = new Derived(1, 2);
var d2 = new Derived(1, 2);
Console.WriteLine(d1 == d2);
- A. 输出
False,继承使相等性失效 - B. 输出
True,相同运行时类型的派生记录按成员值比较 - C. 编译失败,record 之间不能继承
- D. 输出
False,派生记录退化为引用比较
查看答案与解析
正确答案B
解析: 两个 Derived 实例运行时类型一致,Equals 比较 X、Y(先比对 EqualityContract),值相同即为相等。
关键点: record 继承下,相等性 = 运行时类型 + 成员值。
0232 以下代码能否编译?
难度: 进阶
record Base(int X);
class Derived : Base
{
public Derived() : base(1) { }
}
- A. 编译通过
- B. 编译通过,但相等性失效
- C. 编译通过,输出
0 - D. 编译失败,record 只能被 record 继承,普通 class 不能继承 record
查看答案与解析
正确答案D
解析: record class 携带“密封的相等性契约”,其派生类型必须仍是 record;普通 class 继承 record 是编译错误。
关键点: record 的继承树里只能有 record。
0233 以下代码输出什么?
难度: 实战
record Base(int X);
record Derived(int X, int Y) : Base(X);
Console.WriteLine(new Derived(1, 2));
- A. 输出
Derived { X = 1, Y = 2 },派生位置 record 的主构造函数显式调用基类主构造函数 - B. 编译失败,派生 record 必须重新声明
X - C. 输出
Base { X = 1 } - D. 输出
Derived { Y = 2 }
查看答案与解析
正确答案A
解析: record Derived(int X, int Y) : Base(X) 把 X 转发给基类主构造函数;ToString 输出全部成员(基类 + 派生)。
关键点: 位置 record 继承 = 主构造函数 + 基类参数转发。
0234 以下代码输出什么?
难度: 进阶
record Base(int X);
record Derived(int X, int Y) : Base(X);
Base a = new Base(1);
Base b = new Derived(1, 2);
Console.WriteLine(a == b);
- A. 输出
True,成员值相同 - B. 输出
True,Base引用只比较X - C. 输出
False,record 相等性包含运行时类型(EqualityContract),基类与派生类实例永不相等 - D. 编译失败
查看答案与解析
正确答案C
解析: record 的 Equals 先比较 EqualityContract(运行时类型),类型不同直接返回 false,即使成员值相同。这避免了“不同派生记录因成员相同而相等”的破坏性语义。
关键点: record 继承的相等性带“类型指纹”。
0235 record 含有可变引用成员(如 List<int>)时,关于相等性哪项正确?
难度: 进阶
- A. record 会自动深比较集合内容
- B. 相等性与哈希只“浅层”覆盖该成员:引用不同内容相同仍不相等;可变成员参与哈希会在内容变化后破坏
HashSet/字典不变量 - C. 可变引用成员使 record 无法编译
- D. 只要属性是
init,比较就一定安全
查看答案与解析
正确答案B
解析: record 的比较停留在“成员引用”层面;集合内容变化不会更新哈希,放入 HashSet/字典后可能找不到原对象。设计值对象时,参与相等的成员应保持稳定。
关键点: 值语义不等于“对可变内容的深比较”。
0236 以下代码输出什么?
难度: 进阶
class Base
{
public int X { get; init; }
}
class Derived : Base
{
public Derived() { X = 1; }
}
Console.WriteLine(new Derived().X);
- A. 编译失败,派生类不能给基类的 init 属性赋值
- B. 输出
0 - C. 输出
1,但运行抛异常 - D. 输出
1,派生类构造函数在对象仍处于构造阶段时,可以给基类 init 属性赋值
查看答案与解析
正确答案D
解析: init 的赋值窗口覆盖“构造阶段”,包括派生类构造函数(官方示例行为)。构造完成后才禁止。
关键点: init 的边界是“构造阶段”,不是“本类型内部”。
0237 以下代码输出什么?
难度: 实战
class User
{
public required string Name { get; set; }
}
var u = new User { Name = "a" };
u.Name = "b";
Console.WriteLine(u.Name);
- A. 输出
b,required 不要求init,配set的属性同样合法且仍可变 - B. 输出
a - C. 编译失败,required 只能搭配 init
- D. 编译失败,required 属性不可变
查看答案与解析
正确答案A
解析: required 只要求“调用方必须初始化”,访问器可以是 set 甚至字段;初始化之后仍可自由修改。
关键点: required 管“必须有初值”,不管“是否可变”。
0238 以下代码输出什么?
难度: 实战
record R(int X);
var a = new R(1);
var b = new R(1);
Console.WriteLine(Equals(a, b));
Console.WriteLine(ReferenceEquals(a, b));
Console.WriteLine(a == b);
- A.
TrueTrueTrue - B.
FalseFalseFalse - C.
TrueFalseTrue,record 重写了Equals与==,但对象身份仍独立 - D.
TrueTrueFalse
查看答案与解析
正确答案C
解析: record 合成 Equals(值相等)和 ==,但不改变对象身份;ReferenceEquals 永远是引用比较。三者语义不同,容易混为一谈。
关键点: 值相等 ≠ 同一实例;ReferenceEquals 永远看引用。
0239 关于 with 表达式,哪项正确?
难度: 进阶
- A. 任何 class 都可以使用
with - B.
with要求类型提供复制构造函数(record 自动生成),返回与接收者相同运行时类型的新对象,原对象不变 - C.
with会原地修改原对象 - D.
with只能用于 record struct
查看答案与解析
正确答案B
解析: record 自动生成受保护复制构造函数供 with 使用;普通 class 没有它,不能直接用 with。with 生成新对象,原对象不变,且保持运行时类型。
关键点: with 是“复制 + 改”,不是“改 + 回写”。
0240 以下代码输出什么?
难度: 实战
record Base(int X);
record Derived(int X, int Y) : Base(X);
var d = new Derived(1, 2);
var d2 = d with { Y = 9 };
Console.WriteLine(d2);
- A. 编译失败
- B. 输出
Derived { X = 1, Y = 2 } - C. 输出
Base { X = 1 } - D. 输出
Derived { X = 1, Y = 9 },with保持派生类型并只更新指定成员
查看答案与解析
正确答案D
解析: with 复制整个派生对象(含基类成员),应用 Y = 9 修改,其余保持不变;输出展示全部成员。
关键点: 继承场景下 with 依然保持派生类型,不丢基类成员。