返回题库高级 .NET 刷题C# 选择题 · 第 45 / 50 篇

C# 试题 45:C# 13 新特性

0881 以下代码输出什么?

难度: 实战

static void Print(params IEnumerable<int> xs)
{
    Console.WriteLine(xs.Count());
}

Print();
Print(1, 2, 3);
  • A. 编译失败,params 不能用于 IEnumerable<T> 类型
  • B. 输出 0 和 3,无参调用时编译器构造一个空集合传给 params IEnumerable<int>
  • C. 第一行抛 NullReferenceException,第二行输出 3
  • D. 输出 1 和 3,无参调用时使用默认参数 1
查看答案与解析

正确答案B

解析: C# 13 的 params 集合扩展允许 params 使用 IEnumerable<T>Span<T> 等类型。无实参调用时编译器构造一个空集合(对 IEnumerable<T> 生成空数组),因此第一行输出 0,第二行输出 3。前提是这些调用点行为在 C# 13 及对应运行时下成立。

关键点: params 不再只允许数组,接口类型的空调用会构造空集合。

0882 以下代码哪一行编译失败?

难度: 进阶

static void Sum(params Span<int> xs)
{
    Console.WriteLine(xs.Length);
}

int[] arr = { 1, 2, 3 };
List<int> list = new() { 1, 2 };

Sum(1, 2);      // ①
Sum(arr);       // ②
Sum(list);      // ③
  • A. 三行都能编译,分别输出 2、3、2
  • B. ② 编译失败:数组不能传给 params Span<int>
  • C. ③ 编译失败:List<int> 既不能隐式转换为 Span<int>,也不满足逐元素展开的条件
  • D. ① 编译失败:params Span<int> 只能用已存在的 Span 实例调用
查看答案与解析

正确答案C

解析: params Span<int> 在调用点用集合表达式构造(Sum(1, 2) 走栈上分配,Sum(arr) 依赖 int[] → Span<int> 的隐式转换),但 List<int>Span<int> 没有隐式转换,作为单实参也不可展开,故 ③ 编译失败。params 集合的参数形态直接影响调用点能否构造。

关键点: params Span<T> 不接受任意集合类型,只有能构造或能隐式转换的实参才合法。

0883 以下代码输出什么?

难度: 进阶

static void M(params int[] xs) => Console.WriteLine("array");
static void M(params IEnumerable<int> xs) => Console.WriteLine("ienum");

M(new List<int> { 1, 2 });
  • A. 输出 ienumList<int> 作为单个实参按非展开形式直接转换为 IEnumerable<int>
  • B. 输出 arrayparams int[] 重载优先级更高
  • C. 编译失败:两个 params 重载存在歧义
  • D. 运行抛 InvalidCastException
查看答案与解析

正确答案A

解析: 调用 M(new List<int>{1,2}) 时,数组重载无法把 List<int> 转成 int[],而 IEnumerable<int> 重载可用非展开形式直接接收该实参(引用转换,不逐元素复制),因此命中第二个重载。展开形式的转换仅在需要时才参与比较。

关键点: 传集合变量可能走非展开形式,直接把引用交给 IEnumerable<T> 参数。

0884 把自定义集合类型用作 params 集合参数时,哪项要求正确?

难度: 进阶

  • A. 只要实现 IEnumerable<T> 即可,与构造能力无关
  • B. 必须派生自 List<T>Collection<T>
  • C. 只有 IEnumerable<T>Span<T> 能作 params,自定义类型一律不行
  • D. 除 IEnumerable<T>/Span<T> 有特判外,具体集合类型需满足集合表达式的要求:可访问的无参构造函数与 Add 方法,调用点据此构造实例
查看答案与解析

正确答案D

解析: params 集合的实参构造与集合表达式是同一套机制:接口类型和 Span 由编译器特判(生成数组或栈上 Span),具体类型则要求存在适用的无参构造函数与 Add 方法。缺少任一条件,调用点无法构造,代码不能编译。这一约束正是“调用点构造成本取决于目标集合和实参形态”的体现。

关键点: 自定义 params 集合 = 可构造 + 可 Add,不是“实现了接口就行”。

0885 以下代码中编译器为 lock 语句生成什么?

难度: 实战

Lock gate = new();

lock (gate)
{
    Console.WriteLine("critical");
}
  • A. 编译失败,System.Threading.Lock 是值类型
  • B. 对已知的 Lock 类型生成 EnterScope()Scope.Dispose() 的专用作用域模式
  • C. 与锁定 object 一样生成 Monitor.Enter/Monitor.Exit
  • D. 编译通过,但运行抛 SynchronizationLockException
查看答案与解析

正确答案B

解析: C# 13 中 lock 语句识别目标是否为 System.Threading.Lock:是则调用 EnterScope() 并在 finallyDispose(),不再走 MonitorEnter/Exit。只有把 Lock 换成其他类型(如 object)时才会退回通用 Monitor 代码并产生警告。

关键点: lockLock 类型生成专用代码,这是 C# 13 的性能改进点。

0886 以下代码能否编译?

难度: 实战

Lock gate = new();

lock (gate)
{
    await Task.Yield();
}
  • A. 编译失败 CS1996:lock 语句体内不能使用 await
  • B. 编译通过,Lock 专门支持跨 await 保持临界区
  • C. 编译通过,但运行抛 SynchronizationLockException
  • D. 编译通过,await 被编译器忽略
查看答案与解析

正确答案A

解析: 无论锁对象是 object 还是 Lock,C# 编译器都禁止在 lock 语句体内 await(CS1996)。锁由线程持有,await 后的续体可能跑在别的线程上,跨 await 持锁本身就不成立,Lock 类型也不例外。

关键点: “换用 Lock 就能跨 await 持锁”是常见误解。

0887 以下代码会产生什么编译诊断?

难度: 进阶

object gate = new Lock();

lock (gate)
{
    // ...
}
  • A. 编译失败,Lock 不能赋给 object
  • B. 编译通过且无警告,行为与直接锁定 Lock 变量完全一致
  • C. 产生警告 CS9216:Lock 值被转换为其他类型后,lock 走基于 Monitor 的通用锁定
  • D. 运行抛 TypeInitializationException
查看答案与解析

正确答案C

解析: 编译器只有在 lock 目标的静态类型恰好是 System.Threading.Lock 时才生成专用代码;一旦被赋给 object,目标类型变成 object,只能退回 Monitor 语义,并发出 CS9216 警告提示“转换类型会导致可能非预期的基于 Monitor 的锁定”。运行时行为仍是可用的互斥,但拿不到 Lock 的专用路径。

关键点:Lock 装箱成 object 等于放弃了专用锁语义。

0888 以下代码输出什么?

难度: 实战

Lock gate = new();

using (gate.EnterScope())
{
    Console.WriteLine("in scope");
}
  • A. 编译失败,EnterScope 只能配合 lock 语句使用
  • B. 运行抛 SynchronizationLockException
  • C. 输出 in scope,但退出 using 后锁不会释放
  • D. 输出 in scopeEnterScope() 返回的 ref struct Scope 支持 Dispose()using 结束时退出临界区
查看答案与解析

正确答案D

解析: Lock.EnterScope() 返回一个实现了 Dispose()ref struct Lock.Scope,配合 using 在异常时也能释放,等价于 lock 语句生成的代码。Enter/TryEnter/Exit 是另一套需手写 try/finally 的 API,二者不要混用。

关键点: EnterScope() + usingLock 的推荐编程模型之一。

0889 以下代码哪一行编译失败?

难度: 进阶

interface IGet { int Get(); }

ref struct R : IGet
{
    public int Get() => 42;
}

IGet g = new R();   // ①
  • A. ① 编译失败 CS0029:ref struct 不能装箱为接口引用,R 无法转换为 IGet
  • B. 编译通过,g.Get() 输出 42
  • C. 运行抛 InvalidCastException
  • D. ① 之前就编译失败:ref struct 不允许实现接口
查看答案与解析

正确答案A

解析: C# 13 允许 ref struct 实现接口,但接口类型的变量是引用,把 R 赋给 IGet 需要装箱,而 ref struct 禁止装箱,因此编译失败(CS0029)。实现接口只意味着能参与带 allows ref struct 约束的泛型或 ref 安全上下文,不等于可以当作普通引用类型使用。

关键点: ref struct 实现接口 ≠ 可以装箱成接口引用。

0890 以下代码输出什么?

难度: 实战

interface IGet { int Get(); }

ref struct R : IGet
{
    public int Get() => 42;
}

static int Use<T>(T t) where T : IGet, allows ref struct => t.Get();

Console.WriteLine(Use(new R()));
  • A. 编译失败,ref struct 不能作为泛型实参
  • B. 输出 42:allows ref struct 反约束允许 ref struct 实参,接口调用不发生装箱
  • C. 运行抛 NotSupportedException
  • D. 编译失败,allows ref struct 不是合法语法
查看答案与解析

正确答案B

解析: allows ref struct 是 C# 13 的反约束,显式允许 ref struct 作为类型实参,同时禁止该类型实参被装箱。泛型内部通过接口约束调用 t.Get() 走约束调用(constrained call),不会装箱,因此输出 42。去掉 allows ref struct 则会报 CS9244。

关键点:ref struct 参与泛型必须显式声明 allows ref struct

0891 以下代码能否编译?

难度: 进阶

interface IGet { int Get(); }

ref struct R : IGet
{
    public int Get() => 42;
}

var list = new List<R>();
  • A. 编译通过,泛型容器可以存放 ref struct
  • B. 编译通过,但运行抛异常
  • C. 编译失败 CS9244:ref struct 不能作为普通泛型类型实参
  • D. 编译失败,但换用 R[] 数组也不行
查看答案与解析

正确答案C

解析: List<T> 没有 allows ref struct 声明,不能接收 ref struct 实参(CS9244)。这与方法级泛型同理:只有显式声明 allows ref struct 的泛型才能接纳 ref struct。数组例外——R[] 是合法的,因为数组不是需要装箱的泛型容器。

关键点: “泛型容器放不下 ref struct”适用于所有未声明反约束的泛型类型。

0892 关于 ref struct 实现接口后的使用边界,哪项正确?

难度: 进阶

  • A. 实现接口后即可像普通引用类型一样装箱、存字段
  • B. 接口成员必须全部是静态成员才能被实现
  • C. 必须在 unsafe 上下文中才能实现接口
  • D. 实现接口后仍受 ref 安全规则约束:不能装箱、不能作普通泛型实参,泛型场景需显式 allows ref struct
查看答案与解析

正确答案D

解析: ref struct 实现接口只是“契约能力”的扩展,运行时的 ref 安全限制不变:任何需要装箱的用法(转 object、转接口引用、进未声明的泛型容器)都会编译失败。需要泛型支持时必须在类型参数上写 allows ref struct

关键点: 接口实现不豁免 ref struct 的装箱禁令。

0893 以下代码能否编译?

难度: 进阶

static async Task<int> M()
{
    Span<int> s = stackalloc int[2];
    s[0] = 42;
    await Task.Yield();
    Console.WriteLine(s[0]);
    return 0;
}
  • A. 编译失败,async 方法中不能声明任何 ref struct 局部变量
  • B. 编译失败 CS4007:Span<int> 类型的实例不能跨 await 边界保留
  • C. 编译通过并输出 42
  • D. 运行抛 InvalidOperationException
查看答案与解析

正确答案B

解析: C# 13 起 async 方法可以声明 ref struct 局部变量,但变量不能跨 await 边界存活。sawait 之后仍被访问,编译器判定其必须进入状态机,而 ref struct 不能成为状态机字段,故报 CS4007。栈上分配的 Span 本身就随方法栈帧消失,跨 await 使用在语义上也不安全。

关键点: 允许声明 ≠ 允许跨 await 使用。

0894 以下代码输出什么?

难度: 实战

static IEnumerable<int> Get()
{
    int x = 5;
    ref int r = ref x;
    yield return r;
}

Console.WriteLine(Get().First());
  • A. 输出 5:C# 13 起迭代器方法允许声明 ref 局部变量
  • B. 编译失败,迭代器中禁止使用 ref 局部变量
  • C. 编译失败,yieldref 不能共存于同一方法
  • D. 运行抛 InvalidOperationException
查看答案与解析

正确答案A

解析: C# 13 允许迭代器与 async 方法声明 ref 局部变量和 ref struct 局部变量。这里 rx 的别名,在 yield return r 时求值返回 5;由于 r 没有跨 yield 边界使用,编译通过。若在 yield 之后仍引用 r,则与跨 await 一样报错。

关键点: 迭代器内 ref 局部可用,但不能跨 yield 边界存活。

0895 以下代码能否编译?

难度: 进阶

unsafe IEnumerable<int> M()
{
    unsafe
    {
        int* p = stackalloc int[1];
        yield return 1;
    }
}
  • A. 编译通过并输出 1
  • B. 编译失败,迭代器方法不能标记 unsafe
  • C. 编译失败 CS9238:yield return 不能出现在 unsafe 块中
  • D. 运行抛 AccessViolationException
查看答案与解析

正确答案C

解析: C# 13 允许迭代器方法带 unsafe 上下文并使用指针,但规定所有 yield return/yield break 必须位于安全上下文。把 yield 写进 unsafe 块会报 CS9238。正确写法是让 yield 语句留在安全区域,指针操作放在 unsafe 块内。

关键点: 迭代器可 unsafe,但 yield 语句必须在安全上下文。

0896 以下代码输出什么?

难度: 进阶

static async Task<int> M()
{
    Span<int> s = stackalloc int[2];
    s[0] = 42;
    await Task.Yield();
    return 1;
}

Console.WriteLine(M().Result);
  • A. 编译失败 CS4007,Span 不能出现在 async 方法中
  • B. 编译失败,await 之前不能分配 Span
  • C. 输出 1,但这属于未定义行为
  • D. 输出 1:sawait 之后不再被访问,编译器允许其存活到挂起点之前
查看答案与解析

正确答案D

解析: sawait Task.Yield() 之前最后一次被使用,之后不再引用,因此不需要把 s 保存进状态机,编译通过并输出 1。与 0893 对比:跨 await 使用 Span 报错,不跨则合法——编译器按逃逸分析精确判定。

关键点: 逃逸分析决定 ref struct 局部能否出现在 async 方法中。

0897 以下代码能否编译?

难度: 进阶

static int F(int x) => x;
static T F<T>(T t) => t;

var f = F;
Console.WriteLine(f(1));
  • A. 编译通过:无类型实参的泛型候选被剪枝,自然类型由 F(int) 决定
  • B. 编译失败 CS8917,方法组无法推断委托类型
  • C. 编译通过,f 是泛型方法
  • D. 运行抛 AmbiguousMatchException
查看答案与解析

正确答案A

解析: C# 13 的方法组自然类型改进按作用域剪枝候选:var f = F; 未提供类型实参,泛型重载 F<T> 被剪枝,剩余非泛型候选 F(int) 唯一,方法组得到自然类型 Func<int,int>,因此能编译。剪枝使原本会失败或歧义的方法组获得自然类型。

关键点: 泛型候选在无类型实参时被剪枝,是 C# 13 的改进点。

0898 以下代码能否编译?

难度: 进阶

static T F<T>(T t) => t;

var f = F;
  • A. 编译通过,推断为 Func<T, T>
  • B. 编译失败 CS8917:泛型候选在无类型实参时被剪枝,剩余候选为空,无法推断委托类型
  • C. 编译通过,推断为 Action<T>
  • D. 编译通过,f 的类型在运行时确定
查看答案与解析

正确答案B

解析: 剪枝规则把没有提供类型实参的泛型候选全部去掉,而该方法组只剩泛型候选,剪枝后为空,方法组没有自然类型,报 CS8917。需要显式目标类型:Func<int, int> f = F; 或直接给出类型实参的调用。

关键点: 剪枝也可能把候选“剪空”,此时必须显式标注委托类型。

0899 以下代码能否编译?

难度: 实战

var write = Console.Write;
  • A. 编译通过,推断为 Action<string>
  • B. 编译通过,推断为 Action<object>
  • C. 编译失败 CS8917:Console.Write 的重载参数签名各不相同,剪枝后仍无唯一签名,方法组没有自然类型
  • D. 运行抛 MissingMethodException
查看答案与解析

正确答案C

解析: Console.Write 有大量参数不同的重载,且都不是泛型方法,剪枝不适用;这些候选没有共同签名,方法组无自然类型,var 无法推断,报 CS8917。C# 13 的改进只解决“可剪枝”的场景,不代表所有方法组都能 var

关键点: 方法组自然类型不是“万能推断”,重载签名不同仍需显式委托类型。

0900 关于 C# 13「方法组自然类型」改进的核心规则与边界,哪项正确?

难度: 进阶

  • A. C# 13 之后任意方法组都可以用 var 推断
  • B. 剪枝只看候选方法的返回类型
  • C. 该方法组自然类型改进只对扩展方法生效
  • D. 编译器按作用域逐步剪枝候选(无类型实参的泛型方法、约束不满足的候选),使更多方法组获得自然类型;但签名不同的重载仍可能要求显式委托类型
查看答案与解析

正确答案D

解析: 改进的核心是“作用域化 + 剪枝”:先在当前作用域构造候选集,剔除不可能成功的候选(泛型无实参、约束不满足、arity 不匹配等),再按扩展方法作用域逐层推进,更贴近普通重载决议。它扩大了可推断范围,但没有取消“候选签名必须一致”这一底线。

关键点: 剪枝扩大适用范围,显式委托类型仍是兜底手段。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 43C# 试题 43:比较、格式化与解析20 题
  2. 44C# 试题 44:C# 10~12 新特性20 题
  3. 45C# 试题 45:C# 13 新特性20 题
  4. 46C# 试题 46:C# 14 新特性20 题
  5. 47C# 试题 47:unsafe 与原生互操作20 题
ESC

输入关键词开始搜索