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

C# 试题 26:有序、并发与不可变集合

0501 以下代码输出什么?

难度: 实战

var dict = new SortedDictionary<int, string>();
dict[3] = "c";
dict[1] = "a";
dict[2] = "b";

foreach (var kv in dict) Console.Write(kv.Key);
  • A. 321,后进先出
  • B. 213,按哈希序
  • C. 123,SortedDictionary 按键升序枚举,与插入顺序无关
  • D. 312,按插入顺序
查看答案与解析

正确答案C

解析: SortedDictionary 底层是红黑树,枚举始终按比较器(默认 Comparer<TKey>.Default 升序)输出键,插入顺序完全无关。想按插入顺序应使用普通 Dictionary(实现上通常有序但不保证)。

关键点: “Sorted” 是键序,不是插入序。

0502 以下代码输出什么?

难度: 实战

var dict = new SortedDictionary<int, string>(Comparer<int>.Create((x, y) => y.CompareTo(x)));
dict[1] = "a";
dict[3] = "c";
dict[2] = "b";

foreach (var k in dict.Keys) Console.Write(k);
  • A. 123
  • B. 321,自定义比较器反转后按键降序枚举
  • C. 312
  • D. 抛 ArgumentException,比较器不可逆
查看答案与解析

正确答案B

解析: 比较器 y.CompareTo(x) 把顺序颠倒,树按降序组织,枚举输出 3,2,1。自定义比较器必须满足全序(反对称、传递),否则树结构会错乱。

关键点: SortedDictionary 的“顺序”完全由比较器定义。

0503 面对以下两个场景,哪项选型正确?

难度: 进阶

  • A. 频繁插入删除且需保持有序选 SortedDictionary(增删 O(log n));批量构建后主要做查找可选 SortedList(数组存储、缓存友好、内存紧凑)
  • B. 两个场景都选 SortedList,插入永远是 O(1)
  • C. 两个场景都选 SortedDictionary,查找永远 O(1)
  • D. 两者行为完全一致,选哪个无所谓
查看答案与解析

正确答案A

解析: SortedDictionary 用树,插入/删除 O(log n) 适合动态维护;SortedList 用连续数组,插入 O(n)(移位)但占用内存小、缓存命中好,适合先排序后只读/二分查找。混淆二者是常见选型错误。

关键点: SortedList 插入是 O(n),不是“List 就快”。

0504 以下代码输出什么?

难度: 实战

var dict = new ConcurrentDictionary<string, int>();
Console.WriteLine(dict.TryAdd("a", 1));
Console.WriteLine(dict.TryAdd("a", 2));
Console.WriteLine(dict["a"]);
  • A. True / True / 2
  • B. True / False / 2
  • C. False / False / 1
  • D. True / False / 1
查看答案与解析

正确答案D

解析: TryAdd 在键不存在时添加并返回 true,键已存在时不做任何事返回 false。第二次调用被拒绝,字典里仍是 1。

关键点: TryAdd 是“仅新增”,与索引器的“有则覆盖”语义不同。

0505 以下代码输出什么?

难度: 实战

var dict = new ConcurrentDictionary<string, int>();
dict["a"] = 1;
dict["a"] = 2;

Console.WriteLine(dict["a"]);
  • A. 输出 1,索引器只在键不存在时写入
  • B. 抛异常,索引器不能覆盖已有键
  • C. 输出 2,索引器等价于 AddOrUpdate,存在即覆盖
  • D. 输出 0
查看答案与解析

正确答案C

解析: ConcurrentDictionary 的索引器 setter 与普通 Dictionary 相同:键不存在时新增,存在时覆盖。这与 TryAdd 的语义形成对比,也是并发场景最常用的“原子 upsert”。

关键点: 索引器 set = AddOrUpdate;TryAdd = Add only。

0506 以下代码中 calls 的值最有可能是多少?

难度: 进阶

var dict = new ConcurrentDictionary<string, int>();
int calls = 0;

Parallel.For(0, 100, _ =>
    dict.GetOrAdd("k", _ => Interlocked.Increment(ref calls)));

Console.WriteLine(calls);
  • A. 一定是 1
  • B. 一定是 100
  • C. 一定是 0
  • D. 可能大于 1,GetOrAdd 不保证 valueFactory 只执行一次
查看答案与解析

正确答案D

解析: GetOrAdd 的 valueFactory 在锁外执行:多个线程可能同时发现键缺失并各自调用工厂,最终只有一个结果被采纳。因此 calls 往往大于 1,且工厂不能有“只执行一次”的副作用假设。

关键点: 需要一次性初始化请用 Lazy<T> 或先 TryAdd 再处理。

0507 以下代码输出什么?

难度: 进阶

var dict = new ConcurrentDictionary<int, int>();
for (int i = 0; i < 5; i++) dict[i] = i;

foreach (var kv in dict)
{
    dict[kv.Key + 100] = kv.Value;
}

Console.WriteLine(dict.Count);
  • A. 抛 InvalidOperationException,枚举期间不能修改
  • B. 输出 10,并发字典枚举基于快照,遍历中增删不抛异常
  • C. 输出 5
  • D. 死循环
查看答案与解析

正确答案B

解析: ConcurrentDictionary 的枚举器遍历的是快照,遍历期间其他线程的增删(包括本线程在循环体内写入)不会触发版本检查,也不抛异常。循环给 5 个键各加一个新键,最终 Count 为 10。

关键点: 并发字典“快照枚举”与 Dictionary 的“版本检查”行为不同。

0508 以下代码输出什么?

难度: 实战

using System.Collections.Immutable;

ImmutableArray<int> a = default;

Console.WriteLine(a.IsDefault);
try { Console.WriteLine(a.Length); }
catch (NullReferenceException) { Console.WriteLine("NRE"); }
  • A. False / 0
  • B. True / 0
  • C. False / NRE
  • D. True / NRE,未初始化的 ImmutableArray 访问 Length 抛 NullReferenceException
查看答案与解析

正确答案D

解析: default(ImmutableArray<T>) 的底层数组为 null,IsDefault 为 true;访问 Length、索引器等成员时直接抛 NullReferenceException。使用前必须先判 IsDefault/IsDefaultOrEmpty

关键点: 默认构造的 ImmutableArray 不是空数组,是“未初始化”。

0509 以下代码输出什么?

难度: 进阶

using System.Collections.Immutable;

int[] arr = { 1, 2, 3 };
var ia = ImmutableArray.Create(arr);
arr[0] = 99;

Console.WriteLine(ia[0]);
  • A. 99,ImmutableArray.Create(T[]) 直接包装数组而不复制
  • B. 1,创建时已复制
  • C. 抛 InvalidOperationException,不可变数组被修改
  • D. 0
查看答案与解析

正确答案A

解析: ImmutableArray.Create(T[]) 复用传入数组作为底层存储,不做防御性复制,调用方随后修改数组会反映到“不可变”实例中。要隔离必须自行复制后传入。

关键点: 不可变的是“之后不允许修改”,不保证隔离外部输入。

0510 以下代码输出什么?

难度: 实战

using System.Collections.Immutable;

var builder = ImmutableArray.CreateBuilder<int>();
builder.Add(1);
builder.Add(2);

var a = builder.ToImmutable();
builder.Add(3);

Console.WriteLine(a.Length);
  • A. 3
  • B. 0
  • C. 2,ToImmutable 返回当前内容的不可变快照,之后改动 builder 不影响
  • D. 抛异常
查看答案与解析

正确答案C

解析: Builder.ToImmutable() 返回当前内容的不可变快照(内部会复制),此后继续向 builder 添加元素不影响已生成的数组,a.Length 仍是 2。反之,用 builder.MoveToImmutable() 则转移底层数组并清空 builder。

关键点: ToImmutable 是快照,MoveToImmutable 是转移,语义不同。

0511 以下代码输出什么?

难度: 实战

using System.Collections.Frozen;

var frozen = new Dictionary<int, string> { [1] = "a", [2] = "b" }.ToFrozenDictionary();

Console.WriteLine(frozen[1]);
Console.WriteLine(frozen.ContainsKey(3));
  • A. a / True
  • B. a / False
  • C. 1 / False
  • D. 抛异常,FrozenDictionary 不能读取
查看答案与解析

正确答案B

解析: ToFrozenDictionary 一次性构建针对只读高频查找优化的存储,索引器与 ContainsKey 正常工作;键 3 不存在,ContainsKey 返回 false。Frozen 只约束“不可修改”,不改变读取语义。

关键点: 冻结的是可写性,查找行为与普通字典一致。

0512 关于 FrozenDictionary、ImmutableDictionary 与普通 Dictionary 的选型,哪项正确?

难度: 进阶

  • A. FrozenDictionary 支持 Add/Remove,与 ImmutableDictionary 完全等价
  • B. FrozenDictionary 适合频繁增删的写多读少场景
  • C. ImmutableDictionary 的每次修改都原地更新
  • D. FrozenDictionary 适合“启动后固定不变、高频只读”场景,查找更快;ImmutableDictionary 适合需要持久化更新(Add 返回新实例)的场景
查看答案与解析

正确答案D

解析: FrozenDictionary 构建后不可变,专门为只读热路径做哈希布局优化(默认实现可能用完美哈希或更紧凑的桶);ImmutableDictionary 支持 O(log n) 的持久化更新,每次修改返回新实例、旧实例不受影响。二者用途不同。

关键点: “Frozen = 构建一次只读”与“Immutable = 可持久更新”别混为一谈。

0513 以下代码输出什么?

难度: 实战

using System.Collections.Concurrent;

var bc = new BlockingCollection<int>();
bc.Add(1);
bc.CompleteAdding();

Console.WriteLine(bc.Take());
Console.WriteLine(bc.IsAddingCompleted);
try { bc.Take(); }
catch (InvalidOperationException) { Console.WriteLine("empty"); }
  • A. 1 / True / empty,CompleteAdding 后元素取完,再 Take 抛 InvalidOperationException
  • B. 1 / False / empty
  • C. 抛异常,CompleteAdding 后不能 Take
  • D. 1 / True / 0
查看答案与解析

正确答案A

解析: CompleteAdding 只标记“不再添加”,已入队元素仍可正常 Take;等队列清空后再 Take 会抛 InvalidOperationException(表示消费方应停止),而不是返回 0 或阻塞。

关键点: 空 + 已完成的 Take 是异常信号,不是默认值。

0514 以下代码输出什么?

难度: 实战

using System.Collections.Concurrent;

var bc = new BlockingCollection<int>(boundedCapacity: 1);
bc.Add(1);

Console.WriteLine(bc.TryAdd(2, 100));
Console.WriteLine(bc.Count);
  • A. True / 2
  • B. False / 2
  • C. False / 1,容量已满时 TryAdd 等待 100ms 后返回 false
  • D. True / 1
查看答案与解析

正确答案C

解析: 有界容量为 1 且已满时,TryAdd(item, 100) 阻塞等待最多 100ms,期间没有消费者取走元素,超时返回 false,集合仍只有 1 个元素。Add 则会在满时无限阻塞。

关键点: 有界集合的“Try”变体是带超时的阻塞,不是立即失败。

0515 在异步生产者/消费者场景中使用 BlockingCollection,哪项正确?

难度: 进阶

  • A. BlockingCollection 提供原生 TakeAsync 方法,应直接使用
  • B. BlockingCollection 没有 TakeAsync;其 Add/Take 会阻塞线程,现代异步代码优先考虑 Channel
  • C. TakeAsync 返回 ValueTask,永远不会占用线程
  • D. BlockingCollection 阻塞调用不占用任何资源,可随意并发
查看答案与解析

正确答案B

解析: BlockingCollection<T> 只有同步阻塞 API,并不存在 TakeAsync。阻塞的 Add/Take 会挂起线程池线程,异步高并发场景应改用 Channel<T>(WriteAsync/ReadAsync)来避免线程阻塞。

关键点: “有 Async 方法”的假设要先查 API 再信。

0516 以下代码输出什么?

难度: 进阶

using System.Collections.Concurrent;

var bc = new BlockingCollection<int> { 1, 2, 3 };
bc.CompleteAdding();

Console.WriteLine(bc.GetConsumingEnumerable().Count());
Console.WriteLine(bc.GetConsumingEnumerable().Count());
  • A. 3 / 3
  • B. 3 / 抛异常
  • C. 0 / 0
  • D. 3 / 0,GetConsumingEnumerable 会移出元素,第二次枚举为空
查看答案与解析

正确答案D

解析: GetConsumingEnumerable 与普通只读枚举不同:每产出一个元素就把它从集合中移除。第一次 Count() 消费掉全部 3 个元素,第二次枚举时集合已空,结果为 0。

关键点: 消费型枚举只能消费一次,别把结果缓存后再复用。

0517 关于 Channel<T> 与 BlockingCollection<T>,哪项正确?

难度: 进阶

  • A. Channel 提供无阻塞/异步 API(WriteAsync、ReadAsync),适合高吞吐异步场景;BlockingCollection 以阻塞 API 为主
  • B. Channel 只能有界,BlockingCollection 只能无界
  • C. 两者继承同一抽象,可以互换
  • D. Channel 是旧 API,官方建议用 BlockingCollection 替代
查看答案与解析

正确答案A

解析: Channel<T> 是无锁、面向 async/await 的生产者消费者管道,WriteAsync/ReadAsync 不占用线程等待;BlockingCollection 基于同步阻塞原语。官方文档明确建议新异步代码优先使用 Channel。

关键点: 同步阻塞选 BlockingCollection,异步流式选 Channel。

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

难度: 实战

class K { public int V; }

var dict = new SortedDictionary<K, int>();
dict.Add(new K { V = 1 }, 1);
dict.Add(new K { V = 2 }, 2);
  • A. 编译失败,K 必须实现 IComparable
  • B. 第一次 Add 抛 InvalidOperationException
  • C. 第二次 Add 抛 InvalidOperationException:K 未实现 IComparable/IComparable<K> 且未提供比较器,首次比较时失败
  • D. 正常运行,K 按引用比较
查看答案与解析

正确答案C

解析: 插入第一个键时树为空、无需比较,能成功;插入第二个键需要与已有键比较,而 K 既不实现 IComparable 也没有传入比较器,Comparer<K>.Default 在首次 Compare 时抛 InvalidOperationException

关键点: SortedDictionary 要求键可比——要么实现 IComparable,要么传入比较器。

0519 以下代码输出什么?

难度: 进阶

using System.Collections.Immutable;

ImmutableArray<int> a = ImmutableArray.Create(1, 2);
ImmutableArray<int> b = a;

Console.WriteLine(a == b);
Console.WriteLine(a.IsDefault);
  • A. False / False,struct 复制后互不相干
  • B. True / False,复制的是包装结构,底层数组共享,== 按底层数组引用比较
  • C. True / True
  • D. 编译失败,struct 不能赋值
查看答案与解析

正确答案B

解析: ImmutableArray<T> 是包着一维数组的 struct,赋值 b = a 只复制包装(引用同一数组)。== 运算符比较的是底层数组引用,因此为 true;IsDefault 仍为 false。

关键点: struct 复制共享引用,比较运算符比的是内部数组。

0520 以下代码输出什么?

难度: 实战

var dict = new ConcurrentDictionary<string, int>();
dict.AddOrUpdate("a", 1, (key, old) => old + 10);
dict.AddOrUpdate("a", 1, (key, old) => old + 10);

Console.WriteLine(dict["a"]);
  • A. 1,更新工厂从不执行
  • B. 10
  • C. 20
  • D. 11,第一次键不存在走 addValue=1,第二次存在走 updateFactory:1+10
查看答案与解析

正确答案D

解析: AddOrUpdate(key, addValue, updateFactory):键不存在时添加 addValue(1),键已存在时用工厂基于旧值计算(1+10=11)。注意参数顺序是“新增值在前、更新工厂在后”,写反是高频错误。

关键点: AddOrUpdate 的两个分支参数顺序别记反。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 24C# 试题 24:线性集合20 题
  2. 25C# 试题 25:Dictionary、HashSet 与相等性20 题
  3. 26C# 试题 26:有序、并发与不可变集合20 题
  4. 27C# 试题 27:LINQ 延迟执行20 题
  5. 28C# 试题 28:LINQ 分组与连接20 题
ESC

输入关键词开始搜索