C# 问答 02:泛型、集合与 LINQ
021 泛型如何减少装箱并提高类型安全?
难度: 基础
查看参考答案
结论: 类型参数保留具体类型契约,值类型无需先转 object
原因: 编译器可检查成员和转换,运行时也能为值类型实例化合适代码
边界: 接口转换、反射和非泛型 API 仍可能装箱
022 where 约束为什么不仅是文档?
难度: 基础
查看参考答案
结论: 约束决定泛型体内可合法调用的成员并限制类型实参
原因: 它参与编译期类型检查和部分运行时构造验证
边界: notnull 等约束主要影响可空诊断,不自动注入业务校验
023 协变和逆变如何判断方向?
难度: 基础
查看参考答案
结论: 只输出 T 的接口可协变,只输入 T 的接口可逆变
原因: IEnumerable<Dog> 可视为 IEnumerable<Animal>,IComparer<Animal> 可用于比较 Dog
边界: 型变仅适用于引用类型构造,List<T> 本身保持不变
024 IEnumerable<T> 参数意味着什么?
难度: 基础
查看参考答案
结论: 只承诺可枚举,不承诺计数、索引、可重复或无副作用
原因: API 若需多次遍历应物化或提高参数契约
边界: 不要未经确认调用 Count 后再枚举远程或一次性序列
025 IReadOnlyList<T> 是否保证不可变?
难度: 基础
查看参考答案
结论: 不保证,只限制当前引用的写操作
原因: 底层 List 仍可能被其他持有者修改
边界: 需要稳定快照应复制到数组或使用不可变集合
026 List<T> 的容量如何影响性能?
难度: 基础
查看参考答案
结论: 预估 Capacity 可减少扩容和数组复制
原因: 容量不是 Count,过度预留会浪费内存
边界: 只在已知规模和热点数据下设置,不要机械调大
027 Dictionary 键为什么应不可变?
难度: 进阶
查看参考答案
结论: 键入表后若哈希或相等字段改变,所在桶与新哈希不一致
原因: 查询、删除可能再也找不到该键
边界: 可变对象可作为键,但参与相等的状态必须在驻留期稳定
028 如何选择 Dictionary 的字符串比较器?
难度: 进阶
查看参考答案
结论: 协议和标识符常用 Ordinal 或 OrdinalIgnoreCase,用户语言文本按具体文化
原因: 比较器同时影响相等和哈希
边界: 不要依赖当前文化作为跨机器持久标识规则
029 HashSet 适合解决什么问题?
难度: 进阶
查看参考答案
结论: 高频成员测试、去重和集合运算
原因: 平均 O(1) 依赖良好哈希,不提供业务顺序保证
边界: 需要稳定展示顺序时另存顺序或选择有序结构
030 并发集合能否替代所有锁?
难度: 进阶
查看参考答案
结论: 不能,它只保证自身单个或文档声明的复合操作线程安全
原因: 跨多个集合和业务状态的不变量仍需同步
边界: 优先使用 GetOrAdd、AddOrUpdate 等原子 API,注意工厂可能执行多次
031 不可变集合的主要价值是什么?
难度: 进阶
查看参考答案
结论: 修改返回新版本,旧快照可安全共享
原因: 结构共享降低复制但不是零成本
边界: 批量构建用 Builder,极高频只读可考虑 Frozen 集合
032 延迟执行有什么收益和风险?
难度: 进阶
查看参考答案
结论: 可流式组合并避免不需要的工作,但每次枚举会重新执行查询
原因: 源变化、副作用和重复 I/O 会影响结果
边界: 在系统边界明确决定保持流式还是 ToList 快照
033 Any、Count 和 First 应如何选择?
难度: 进阶
查看参考答案
结论: 按语义选最弱操作,只问存在用 Any,只取首项用 First
原因: Count 对无快速计数接口的源可能完整遍历
边界: 不要为了一个元素把整个序列 ToList
034 First 与 Single 的异常语义差异是什么?
难度: 进阶
查看参考答案
结论: First 验证至少一个,Single 还验证不超过一个
原因: Single 能表达数据库唯一性假设但可能额外读取
边界: OrDefault 版本还要处理 default 是合法值的歧义
035 GroupBy 为什么可能占用大量内存?
难度: 实战
查看参考答案
结论: Enumerable.GroupBy 通常先读取完整源并构建分组查找
原因: 高基数或无限流会导致高内存或无法产出
边界: 流式分组需要输入有序并使用专门算法或数据库聚合
036 Join 与 GroupJoin 如何区分?
难度: 实战
查看参考答案
结论: Join 产生内连接平面结果,GroupJoin 为每个外部元素提供匹配组
原因: 左外连接可在 GroupJoin 后 DefaultIfEmpty
边界: 选择取决于结果形状而不只是 SQL 名称
037 如何避免 LINQ 热路径的隐藏分配?
难度: 实战
查看参考答案
结论: 识别闭包、迭代器、接口枚举和物化,使用静态 Lambda 或直接循环验证热点
原因: 可读性通常优先,优化必须有基准
边界: 不要假设所有 LINQ 都慢,也不要无条件重写
038 Span 为什么不能作为普通集合字段?
难度: 实战
查看参考答案
结论: Span 是 ref struct,引用安全不允许逃逸到普通堆对象
原因: 它可能指向栈或可移动内存,生命周期必须静态受控
边界: 长期持有改用 Memory<T> 或明确所有权的数组
039 ArrayPool 使用时要遵守什么所有权?
难度: 实战
查看参考答案
结论: 租借者独占使用有效区间并在最后使用后归还
原因: 返回数组可能更大且含旧数据,敏感内容需清理
边界: 异步操作完成前不能归还,归还后不能继续访问
040 如何为集合 API 表达所有权?
难度: 实战
查看参考答案
结论: 说明调用方能否修改、是否复制、是否缓存和枚举成本
原因: 接口只描述操作面,不能替代生命周期文档
边界: 公共边界可返回快照,内部热点可用租借缓冲并明确释放