C# 问答 01:语言基础与面向对象
001 var 和 dynamic 的核心差异是什么?
难度: 基础
查看参考答案
结论: var 是编译期静态类型推断,dynamic 把成员绑定推迟到运行时
原因: 静态类型让错误更早暴露并支持优化,dynamic 调用点依赖运行时绑定器
边界: dynamic 适合 COM、动态数据等边界,不应替代普通静态建模
002 const、readonly 和 static readonly 如何选择?
难度: 基础
查看参考答案
结论: 编译期固定值用 const,构造后固定的实例状态用 readonly,运行时计算的类型级值用 static readonly
原因: 三者的赋值时机、存储位置和调用方版本语义不同
边界: 公共 const 会嵌入调用方,修改值需让调用方重新编译
003 值类型和引用类型的复制语义有什么区别?
难度: 基础
查看参考答案
结论: 值类型变量通常保存并复制数据,引用类型变量复制对象引用
原因: 这影响参数传递、相等性、可变性与分配分析
边界: ref、in、Span 等能显式改变存储位置共享方式,不能只看 class 或 struct 关键词
004 为什么启用可空引用类型仍需要运行时校验?
难度: 基础
查看参考答案
结论: 可空引用类型是编译期流分析,不改变 CLR 中引用可以为 null 的事实
原因: 反射、旧程序集、反序列化和不可信输入都可能绕过注解
边界: 公共边界应同时写准确注解并执行参数或数据验证
005 装箱何时发生,如何识别?
难度: 基础
查看参考答案
结论: 值类型转 object、非泛型接口或某些委托路径时可能装箱
原因: 装箱复制值到堆对象并可能增加分配和虚调用
边界: 用泛型接口、IEquatable<T> 和分析器优化热路径,非热路径先重视可读性
006 构造函数中为什么不建议调用可重写成员?
难度: 基础
查看参考答案
结论: 派生对象尚未完成初始化,可重写调用可能观察到不完整状态
原因: 基类构造阶段的动态分派会进入派生实现
边界: 构造逻辑应依赖基类自身不变量,必要初始化通过参数或非虚私有方法完成
007 override 与 new 隐藏成员有什么不同?
难度: 进阶
查看参考答案
结论: override 参与虚槽动态分派,new 按表达式静态类型隐藏基成员
原因: 通过基类引用调用时两者结果不同
边界: 只有基成员可虚分派时才能 override,隐藏应显式 new 并谨慎使用
008 接口和抽象类如何选择?
难度: 进阶
查看参考答案
结论: 跨继承树的能力契约用接口,共享状态、构造和受保护实现用抽象类
原因: 类可实现多个接口但只能继承一个类
边界: 默认接口实现适合有限契约演进,不应把接口变成状态基类
009 默认接口实现解决了什么,又有什么边界?
难度: 进阶
查看参考答案
结论: 它允许给已发布接口增加带默认体的成员,降低部分实现方破坏
原因: 调用解析、运行时支持和多接口冲突仍需测试
边界: 它不会把默认成员自动变成类的普通 public 成员
010 record 适合哪些模型?
难度: 进阶
查看参考答案
结论: record 适合以值和数据组成定义身份的模型
原因: 默认相等性和 with 是浅层语义,可变引用成员仍共享
边界: 实体身份、可变生命周期或 ORM 代理模型需单独评估
011 required 是否等于运行时必填校验?
难度: 进阶
查看参考答案
结论: 不等于,它主要要求支持该语言的调用方在创建时初始化成员
原因: 反射、反序列化及 SetsRequiredMembers 可绕开编译期检查
边界: 外部输入仍需验证框架和领域不变量
012 结构体设计为什么强调小、不可变?
难度: 进阶
查看参考答案
结论: 小型不可变值能降低复制和别名修改的复杂性
原因: 大结构体按值传递可能昂贵,可变结构体在属性和只读接收者上易产生副本陷阱
边界: 先按语义选择类型,再用 readonly struct、in 和基准优化热点
013 protected internal 与 private protected 如何区分?
难度: 进阶
查看参考答案
结论: 前者是同程序集或派生类型,后者是同程序集且为派生类型
原因: 一个是 OR,一个是 AND
边界: 跨程序集扩展点通常考虑 protected,内部派生实现细节才考虑 private protected
014 对象初始化器的执行顺序是什么?
难度: 进阶
查看参考答案
结论: 先完成构造函数,再按源码顺序执行成员初始化赋值
原因: 因此属性 setter 可观察到已构造对象但对象仍未交给调用方
边界: 不要把初始化器误认为构造函数参数,关键不变量优先在构造函数建立
015 什么时候使用显式接口实现?
难度: 实战
查看参考答案
结论: 接口同名冲突或只希望通过契约暴露成员时使用
原因: 显式成员不参与类实例普通成员查找
边界: 调用方必须先以对应接口类型引用对象
016 静态抽象接口成员有什么价值?
难度: 实战
查看参考答案
结论: 让泛型算法通过约束调用实现类型的静态运算
原因: 它支持泛型数学而无需 dynamic 或反射
边界: 调用依赖受约束类型参数,不能从普通接口实例调用
017 如何设计稳定的相等性契约?
难度: 实战
查看参考答案
结论: 先定义身份字段,再让 Equals、GetHashCode 和比较器保持一致
原因: 相等对象必须有相同哈希码,参与哈希的字段入表后应稳定
边界: 继承层次和值对象集合成员会增加复杂度,必要时使用密封类型
018 为什么公共 API 不应随意暴露可变集合?
难度: 实战
查看参考答案
结论: 调用方可能修改内部状态并破坏不变量
原因: 返回 IReadOnlyList 只限制该引用,真正快照需复制或不可变集合
边界: 高性能场景还需明确所有权,避免每次返回都无条件复制
019 模式匹配相对强制转换的优势是什么?
难度: 实战
查看参考答案
结论: 它把类型测试、绑定和结构条件组合成可检查的控制流
原因: 失败分支不抛转换异常,编译器还能分析变量作用域
边界: 属性 getter 仍可能有副作用,复杂模式应保持可读
020 如何审查一个领域类型的 API?
难度: 实战
查看参考答案
结论: 检查身份、可变性、空值、异常、线程安全和版本演进契约
原因: 调用方依赖的是可观察行为而不是当前实现
边界: 把不变量放在构造和方法边界,并用测试覆盖非法状态