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

C# 试题 50:综合代码阅读与易错点

0981 以下代码输出什么?

难度: 实战

class Counter
{
    public int N;
    public int Tick => ++N;
}

var c = new Counter();
Console.WriteLine(c.Tick + c.Tick);
  • A. 输出 2,属性值在同一表达式中只求值一次
  • B. 输出 3,Tick 的 getter 被求值两次,副作用各发生一次
  • C. 输出 1
  • D. 编译失败,同一属性不能在表达式中出现两次
查看答案与解析

正确答案B

解析: Tick 是表达式体属性,每次访问 getter 都会执行 ++Nc.Tick + c.Tick 按从左到右求值两次 getter:第一次 N 变为 1 并返回 1,第二次变为 2 并返回 2,和为 3。属性访问不会自动缓存,求值次数就是访问次数。

关键点: 属性访问次数等于 getter 执行次数,副作用不可假设只发生一次。

0982 以下代码输出什么?

难度: 进阶

int[] a = { 10, 20, 30 };
int i = 0;

Console.WriteLine(a[i++] + a[i++]);
  • A. 输出 20
  • B. 输出 40
  • C. 结果未定义,i++ 的求值顺序由编译器决定
  • D. 输出 30,C# 规定二元运算的操作数按从左到右求值
查看答案与解析

正确答案D

解析: C# 语言规范固定了求值顺序:二元运算符先求值左操作数再求值右操作数。a[i++] 先取 a[0]i 变 1),再取 a[1]i 变 2),结果为 10 + 20 = 30。这与 C/C++ 中“副作用顺序未指定”的语义不同。

关键点: C# 求值顺序是确定的从左到右,不存在 C/C++ 式未定义行为。

0983 以下代码输出什么?

难度: 进阶

bool F() { Console.Write("F"); return true; }
bool G() { Console.Write("G"); return false; }

bool r = F() || G() && F();
Console.WriteLine(r);
  • A. 先输出 F,再输出 True&& 优先级高于 ||F()true 后直接短路
  • B. 先输出 FGF,再输出 True
  • C. 先输出 FG,再输出 False
  • D. 先输出 F,再输出 False
查看答案与解析

正确答案A

解析: && 优先级高于 ||,表达式等价于 F() || (G() && F())F() 打印 F 并返回 true|| 短路,右侧整体不再求值;最终结果为 true,由 Console.WriteLine 输出 True

关键点: 优先级决定结合方式,短路决定哪些操作数真正被求值。

0984 以下代码输出什么?

难度: 实战

struct Counter
{
    public int Value;
    public void Increment() => Value++;
}

class Wrapper
{
    public Counter Counter { get; set; } = new Counter();
}

var w = new Wrapper();
w.Counter.Increment();
Console.WriteLine(w.Counter.Value);
  • A. 输出 1,Increment 修改了属性持有的结构体
  • B. 编译失败,结构体不能定义实例方法
  • C. 输出 0,w.Counter 的 getter 返回临时副本,Increment() 修改的是副本
  • D. 运行抛 NullReferenceException
查看答案与解析

正确答案C

解析: 结构体是值类型,属性 getter 按值返回副本。w.Counter.Increment() 实际是在 getter 返回的临时副本上调用 Increment,原字段 Value 保持 0,修改结果静默丢失。

关键点: 对属性返回的结构体调用修改方法等于修改副本,改动不生效。

0985 以下代码哪一行无法编译?

难度: 进阶

struct Point { public int X; }

Point[] arr = { new Point() };
arr[0].X = 5;                       // 第 1 行

var list = new List<Point> { new Point() };
list[0].X = 5;                      // 第 2 行

Console.WriteLine(arr[0].X);
  • A. 第 1 行
  • B. 第 2 行,List<T> 索引器返回的是元素副本,修改其成员属于修改“非变量”的返回值
  • C. 两行都能编译
  • D. 两行都无法编译
查看答案与解析

正确答案B

解析: 数组元素是变量,arr[0].X = 5 合法;List<T> 的索引器 getter 返回 T 的副本,list[0].X = 5 报 CS1612(不能修改非变量的返回值)。要改列表元素应整体替换元素(如 list[0] = new Point { X = 5 })或通过 ref 访问。

关键点: 数组元素可修改成员,List<T> 索引器返回副本时成员赋值是编译错误。

0986 以下代码输出什么?

难度: 进阶

struct Point
{
    public int X;
    public void Move(int dx) => X += dx;
}

class Box
{
    readonly Point p;

    public Box() { p = new Point { X = 1 }; }

    public void Run()
    {
        p.Move(5);
        Console.WriteLine(p.X);
    }
}

new Box().Run();
  • A. 输出 6
  • B. 编译失败,readonly 字段不能调用实例方法
  • C. 运行抛 InvalidOperationException
  • D. 输出 1,访问 readonly 结构体字段的成员会先复制副本,Move 修改的是副本
查看答案与解析

正确答案D

解析: 编译器无法保证实例方法不会修改结构体,因此对 readonly 字段 p 调用 Move 时先制作防御性副本,副本的 X 变成 6,原字段仍为 1。直接在方法里给成员赋值(p.X = ...)则是编译错误,二者要区分。

关键点: readonly 结构体字段的成员调用产生防御性副本,修改静默丢失。

0987 以下代码输出什么?

难度: 进阶

interface IInc { void Inc(); }

struct Counter : IInc
{
    public int Value;
    public void Inc() => Value++;
}

Counter c = new Counter();
((IInc)c).Inc();
((IInc)c).Inc();
Console.WriteLine(c.Value);
  • A. 输出 0,每次装箱都产生独立副本,接口调用修改的是装箱对象
  • B. 输出 2
  • C. 输出 1
  • D. 编译失败,结构体不能实现接口
查看答案与解析

正确答案A

解析: 结构体转换为接口必然装箱,((IInc)c) 每次创建新的装箱副本;Inc() 通过接口分派修改的是装箱对象里的字段,原变量 c 不受影响,两次调用后仍为 0。

关键点: 装箱即复制,接口分派修改的是副本而非原值类型变量。

0988 以下代码输出什么?

难度: 实战

var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1);

numbers.Add(4);

Console.WriteLine(query.Count());
  • A. 输出 2
  • B. 输出 4
  • C. 输出 3,Where 延迟执行,Count() 枚举时读取的是当前源
  • D. 运行抛 InvalidOperationException
查看答案与解析

正确答案C

解析: Where 返回延迟执行的查询,只保存源引用与谓词,Count() 触发枚举时源已包含 4,满足 n > 1 的是 2、3、4,共 3 个。查询结果取决于“枚举那一刻”的源状态。

关键点: 延迟执行意味着查询结果与枚举时的源内容绑定,而非创建查询时。

0989 以下代码输出什么?

难度: 实战

var funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
    funcs.Add(() => i * 10);
}

foreach (var f in funcs)
{
    Console.Write(f() + " ");
}
  • A. 输出 0 10 20
  • B. 输出 30 30 30 for 的循环变量只有一个实例,被所有闭包共享
  • C. 输出 0 0 0
  • D. 编译失败,循环变量不能被闭包捕获
查看答案与解析

正确答案B

解析: for 的循环变量在整段循环中只有一个实例,三个闭包捕获的是同一个 i;循环结束后 i == 3,每次调用都输出 30。C# 5+ 的 foreach 每次迭代使用独立变量,不受此问题影响。

关键点: for 闭包共享循环变量是经典陷阱,需局部副本或 foreach 规避。

0990 以下代码运行结果是什么?

难度: 实战

var list = new List<int> { 1, 2, 3 };

foreach (var x in list)
{
    if (x == 2)
    {
        list.Add(4);
    }
}
  • A. 运行抛 InvalidOperationException,枚举期间修改集合触发版本检查
  • B. 正常结束,List<T> 允许在 foreach 中追加元素
  • C. 运行抛 NotSupportedException
  • D. 无限循环
查看答案与解析

正确答案A

解析: List<T> 的枚举器带有版本号,Add 会使版本号变化;foreach 在下一次 MoveNext 时检测到版本不一致,抛 InvalidOperationException。集合修改与枚举不能交错进行。

关键点: 枚举期间修改源集合是运行时异常,不是静默行为或死循环。

0991 以下代码输出什么?

难度: 进阶

var data = new List<int> { 1, 2, 3 };
int calls = 0;
var query = data.Select(x => { calls++; return x * 2; });

_ = query.ToList();
_ = query.ToList();

Console.WriteLine(calls);
  • A. 输出 3
  • B. 输出 9
  • C. 编译失败,投影委托中不允许有副作用
  • D. 输出 6,延迟查询每次枚举都重新执行投影
查看答案与解析

正确答案D

解析: Select 延迟执行且不缓存结果,每次 ToList() 都重新遍历源并执行投影委托;两次枚举共调用 6 次。若投影有副作用,枚举次数会直接放大副作用,应避免依赖。

关键点: 延迟查询多次枚举 = 多次重新执行,副作用次数与枚举次数成正比。

0992 以下代码输出什么?

难度: 实战

static string M()
{
    try
    {
        return "try";
    }
    finally
    {
        Console.Write("finally ");
    }
}

Console.WriteLine(M());
  • A. 输出 try
  • B. 输出 try finally
  • C. 输出 finally tryfinally 在方法真正返回前执行
  • D. 编译失败,returnfinally 不能共存
查看答案与解析

正确答案C

解析: return 先计算返回值并保存,随后执行 finally,最后才把控制权交回调用方,因此先打印 finally 再输出 tryfinally 的清理逻辑必然先于方法返回发生。

关键点: return 表达式先求值,但 finally 先执行,方法才算真正返回。

0993 以下代码输出什么?

难度: 进阶

static void M()
{
    try
    {
        throw new InvalidOperationException("A");
    }
    finally
    {
        throw new FormatException("B");
    }
}

try
{
    M();
}
catch (FormatException e)
{
    Console.WriteLine("Format: " + e.Message);
}
catch (InvalidOperationException e)
{
    Console.WriteLine("Invalid: " + e.Message);
}
  • A. 输出 Invalid: A
  • B. 输出 Format: Bfinally 抛出的异常取代在途的原始异常
  • C. 依次输出 Invalid: AFormat: B
  • D. 运行抛 AggregateException
查看答案与解析

正确答案B

解析: 异常沿栈展开经过 finally 时,finally 又抛出 FormatException,新异常取代在途的 InvalidOperationException(原始异常不会自动成为内部异常),因此外层只捕获到 Format: B

关键点: finally 中抛异常会覆盖原异常,清理逻辑中应避免抛出。

0994 以下代码输出什么?

难度: 实战

class R : IDisposable
{
    public R() => throw new InvalidOperationException("ctor");
    public void Dispose() => Console.Write("D");
}

try
{
    using (var r = new R()) { }
}
catch (InvalidOperationException)
{
    Console.Write("C");
}
  • A. 输出 C,构造失败时资源从未取得,Dispose 不会被调用
  • B. 输出 DC
  • C. 输出 CD
  • D. 编译失败
查看答案与解析

正确答案A

解析: using 展开为 try/finally 包围语句体,而 new R() 的求值在 try 之前;构造函数抛异常时变量从未赋值,Dispose 不会被调用,异常被外层 catch 捕获并打印 C

关键点: 构造失败不触发 Dispose,资源获取与释放的边界要按展开语义理解。

0995 以下代码输出什么?

难度: 进阶

static bool ShouldLog(Exception e)
{
    Console.Write("L");
    return false;
}

try
{
    try
    {
        throw new InvalidOperationException();
    }
    catch (Exception e) when (ShouldLog(e))
    {
        Console.Write("C");
    }
}
catch (InvalidOperationException)
{
    Console.Write("P");
}
  • A. 输出 C
  • B. 输出 LCP
  • C. 编译失败,when 过滤器必须返回 bool
  • D. 输出 LP,过滤器返回 false 时跳过该 catch,异常继续向外传播
查看答案与解析

正确答案D

解析: when 过滤器在 catch 体之前执行:ShouldLog 打印 L 并返回 false,该 catch 不匹配,异常继续向外传播,被外层 catch 捕获并打印 P。过滤器返回 true 才会进入 catch 体。

关键点: 过滤器返回 false 等于该 catch 不匹配,异常继续传播。

0996 以下代码输出什么?

难度: 实战

var data = new[]
{
    ("x", 1),
    ("y", 1),
    ("z", 0)
};

var sorted = data.OrderBy(t => t.Item2).Select(t => t.Item1);
Console.WriteLine(string.Join("", sorted));
  • A. 输出 xyz
  • B. 输出 zyx
  • C. 输出 zxyOrderBy 是稳定排序,相等键保持原相对顺序
  • D. 输出结果不确定
查看答案与解析

正确答案C

解析: 按文档契约,OrderBy 是稳定排序:相等键保持源顺序。键 0 的 z 排最前,键 1 的 xy 保持原相对顺序,因此输出 zxyArray.Sort 等不稳定排序在相等键上不能给出同样保证。

关键点: 稳定排序契约决定相等键的先后,不能想当然套用不稳定排序的直觉。

0997 以下代码运行结果是什么?

难度: 实战

var nums = new List<int> { 1, 2 };
int x = nums.SingleOrDefault();
Console.WriteLine(x);
  • A. 输出 0
  • B. 运行抛 InvalidOperationExceptionSingleOrDefault 只在序列为空时返回默认值,多于一个元素仍抛异常
  • C. 输出 1
  • D. 运行抛 ArgumentException
查看答案与解析

正确答案B

解析: SingleOrDefault 的契约是:空序列返回默认值;恰好一个元素返回该元素;多于一个元素抛 InvalidOperationException。序列有两个元素,必然抛异常而不是返回 0,常见误解是把它当成“绝不抛异常”的版本。

关键点: SingleOrDefault 的默认值只对空序列生效,多个元素仍然抛异常。

0998 关于把可变结构体用作 Dictionary<TKey, TValue> 的键,哪项正确?

难度: 进阶

  • A. 字典保存的是键的引用,插入后修改键字段不会影响查找
  • B. 字典会自动对键做防御性复制,修改键字段不会生效
  • C. 可变结构体无法实现 GetHashCode,不能作为键
  • D. 插入后若修改了参与 GetHashCode/Equals 的字段,哈希桶错位,后续查找可能失败或得到错误结果,因此可变结构体不宜作键
查看答案与解析

正确答案D

解析: 字典按键的哈希值定位桶。可变结构体插入后再修改参与 GetHashCode/Equals 的字段,哈希值变化,原桶位置已无法定位该键,查找可能失败或命中错误位置;且结构体按键值复制,任何“修改”都只影响副本。键的哈希稳定性是字典契约的前提。

关键点: 键在存续期间必须哈希不变,可变结构体违反该前提。

0999 关于 LINQ 的执行模式,哪项正确?

难度: 实战

  • A. 延迟执行意味着查询每次被枚举都会重新执行(可能重新读取源),ToList/ToArray 等是立即物化
  • B. 所有 LINQ 操作符都是延迟执行
  • C. Where 在调用时立即过滤并返回新集合
  • D. OrderBy(...).ToList()OrderBy 会先执行并缓存结果,ToList 只是读取缓存
查看答案与解析

正确答案A

解析: WhereSelectOrderBy 等返回延迟查询,枚举时才对源求值;ToList/ToArray/Count 等触发立即执行并物化结果。延迟查询没有“提前执行并缓存”的中间状态,每次枚举都是重算。

关键点: 延迟执行 = 每次枚举重算;要固定结果必须立即物化。

1000 阅读陌生 API 的契约时,哪项做法最可靠?

难度: 实战

  • A. 方法名足以说明行为,例如 Get 开头的方法一定没有副作用
  • B. 只凭一次正常路径的测试结果即可确定全部边界行为
  • C. 应结合签名、文档、异常约定与针对性测试确认边界,方法名与单一用例不足以证明行为
  • D. 只要代码能编译,就说明调用符合 API 契约
查看答案与解析

正确答案C

解析: API 契约由签名、文档、异常约定和实际边界行为共同定义;方法名只是提示,一次正常路径测试无法覆盖空值、边界与异常路径。可靠做法是读文档契约并用针对性测试验证,例如 SingleOrDefaultOrderBy 这类“名字与语义有落差”的 API。

关键点: 名称与单一用例不足以下结论,契约要按签名、文档与边界测试综合判断。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 46C# 试题 46:C# 14 新特性20 题
  2. 47C# 试题 47:unsafe 与原生互操作20 题
  3. 48C# 试题 48:装箱、闭包与性能20 题
  4. 49C# 试题 49:测试、调试与分析器20 题
  5. 50C# 试题 50:综合代码阅读与易错点20 题
ESC

输入关键词开始搜索