Chapter 2. C# 2
覆盖Generics、Nullable value types、Simplified delegate creation、Iterators与Minor features,解释compile-time contract和compiler-generated state。
学习目标
- 能比较pre-generic object collection与generic type/method的compile-time、runtime representation和boxing差异
- 能推导Nullable value types与Simplified delegate creation的conversion、default、capture和invocation语义
- 能绘制iterator construction、MoveNext、Current与Dispose状态,复现partial enumeration的finally cleanup
机制总览
Chapter 2. C# 2:机制路径
- 1
为什么C 2把Runtime猜测前移到Compiler C…
C 1可以用 object 存任意值、手写delegate constructor、手写 IEnumerator ,但正确关系主要靠developer记忆和runtime cast。C 2的主线是把element type、absence、callable target和iteration stat…
- 2
Generics
Pre-generic ArrayList 只知道 object :value type插入要boxing,读取要cast,错误可能在很远的consumer处出现。
- 3
Nullable value types
Nullable 让non-nullable value type同时表达一个T或absence,syntax是 T?
章级决策实验
Chapter 2. C# 2:机制与证据
切换《Chapter 2. C# 2》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么C 2把Runtime猜测前移到Compiler C…
C 1可以用 object 存任意值、手写delegate constructor、手写 IEnumerator ,但正确关系主要靠developer记忆和runtime cast。C 2的主线是把element type、absence、callable target和iteration stat…
可核验证据
以明确的 LangVersion 与目标框架构建「为什么C 2把Runtime猜测前移到Compiler C…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
学完《Chapter 2. C# 2》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 2. C# 2:失效与核验
为什么C 2把Runtime猜测前移到Compiler C…
典型失效
若解释「为什么C 2把Runtime猜测前移到Compiler C…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「为什么C 2把Runtime猜测前移到Compiler C…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Generics
典型失效
若解释「Generics」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Generics」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Nullable value types
典型失效
若解释「Nullable value types」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Nullable value types」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
为什么C# 2把Runtime猜测前移到Compiler Contract
C# 1可以用object存任意值、手写delegate constructor、手写IEnumerator,但正确关系主要靠developer记忆和runtime cast。C# 2的主线是把element type、absence、callable target和iteration state交给compiler验证或生成,让错误更早、ceremony更少,同时保留可检查的runtime model。
先预测:List<string>是否是List<object>子类型;int? + int?只要一边有值是否就有值;调用iterator method是否立即执行body;anonymous method是否总不分配。答案都是否。
Generics
Pre-generic ArrayList只知道object:value type插入要boxing,读取要cast,错误可能在很远的consumer处出现。List<T>让add、indexer、enumerator都共享T,compiler能追踪关系;runtime也保留constructed generic type,不是Java式type erasure。
What can be generic?
Classes、structs、interfaces、delegates和methods都可generic。T属于instance state或多个members共同contract时用generic type;只参与一个operation时用generic method并让type inference减少call-site ceremony。Constraint不是documentation,而是compiler可调用哪些members的证明。
public static T Max<T>(T left, T right, IComparer<T> comparer) =>
comparer.Compare(left, right) >= 0 ? left : right;Runtime通常可为reference-type instantiations共享machine code,为value types生成specialized code以处理不同layout;具体策略是runtime实现细节。API correctness不能依赖“每个T一定一份code”,performance结论也必须profile。
切换object、generic、method与constraint
Nullable value types
Nullable<T>让non-nullable value type同时表达一个T或absence,syntax是T?。HasValue区分状态,Value在empty时抛异常,GetValueOrDefault返回default但会丢失“真的为default”和“没有值”的区别。Domain API应保留absence直到有明确default policy的boundary。
Lifted operators把underlying operator提升到nullable operands。多数算术比较遇null产生null或false,但bool?的&/|具有三值逻辑特例;不能凭“null传播”一句话推导全部。Nullable boxing也特殊:empty boxes为null,有值时box underlying T,而不是boxed Nullable<T>。
int? quantity = null;
int? doubled = quantity * 2; // null
object? boxed = quantity; // null
object boxedValue = (int?)42; // boxed Int32Simplified delegate creation
C# 2允许method group conversion,省掉显式new Comparison<Product>(CompareProducts);overload resolution仍必须找到compatible target。Anonymous method把短callback body放在call site,并可capture外部variable storage,compiler通常生成hidden method和closure object。
Delegate是immutable invocation list。Combine/remove产生新delegate;multicast按顺序调用,非void只返回最后target结果,任一target抛异常会中断后续调用。Language simplification不定义thread safety、subscriber isolation或lifetime,这些由API contract补充。
↡把一组同名methods按目标delegate signature做overload resolution并创建delegate的implicit conversion。切换nullable、method group、anonymous method与null delegate
Iterators
Iterator block包含yield return/yield break,compiler生成实现IEnumerable<T>/IEnumerator<T>的state machine。调用method通常只创建sequence object;body在MoveNext时执行,local state被提升为fields,Current暴露最近一次yield value。
每次可重复枚举的iterator应创建独立enumerator state;一个enumerator本身通常不可并发使用。Side effects、time、random和external source会在每次enumeration重跑,因此API要声明live sequence还是snapshot。
foreach通过try/finally确保enumerator Dispose。Iterator内部的try/finally会被lowering到state transitions;consumer提前break也应触发cleanup。yield return不能随意放入所有catch/finally组合,compiler restrictions正是为保持state-machine语义可实现。
public static IEnumerable<string> ReadLines(TextReader reader)
{
string? line;
while ((line = reader.ReadLine()) is not null)
yield return line;
}切换construction、MoveNext、Dispose与minor features
Minor features
Partial types允许generated与handwritten declarations合并为一个type,适合tool ownership boundary但不应按业务职责随意拆散;static classes让compiler禁止instance;namespace aliases解决name collision;different accessibility与fixed buffer等features各有特定场景。它们不像generics/iterator共享一条runtime机制,但都把意图从convention提升为language rule。
本章回顾:Type Relation和Control State都可由Compiler证明
- Generics保留T关系、减少cast/boxing,并以constraint表达可用能力。
- Nullable<T>把value absence纳入type,lifted operators与boxing有精确定义。
- Method group与anonymous method简化delegate creation,但capture和multicast lifetime仍需API定义。
- Iterator method call与body execution分离,MoveNext驱动generated state machine。
- Partial enumeration必须Dispose并运行finally;重复枚举是否重做work是公开contract。
练习
问题 1:为什么List<Dog>不能赋给List<Animal>,怎样提供安全只读view?
问题 2:nullable、delegate与closure三个null/lifetime问题如何区分?
问题 3:iterator读取resource时,谁应拥有resource?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- parameterized type contract
- lifted operator
- method group conversion
- iterator state machine
- partial enumeration contract