Chapter 7. C# 5 bonus features
覆盖Capturing variables in foreach loops与Caller information attributes,深入解释iteration storage、compiler-emitted arguments和diagnostic provenance边界。
学习目标
- 能比较C# 5前后foreach iteration-variable lowering,并复现foreach、for与mutable captured object的不同结果
- 能推导CallerMemberName/FilePath/LineNumber如何由caller compiler写入optional arguments,判断explicit override行为
- 能设计property notification、logging与wrapper中的diagnostic provenance,避免把caller info当security或stable schema
机制总览
Chapter 7. C# 5 bonus features:机制路径
- 1
为什么两个“小Feature”都在修复Hidden Sou…
Closure bug来自reader以为每轮有新value,而compiler曾让delegates共享一个storage;caller info解决callee想知道call site,却不想每处重复脆弱string。两者都把source context变成明确compiler rule:一个调…
- 2
Capturing variables in foreac…
旧语言语义可理解为iteration variable位于loop外,每次赋新值;所有anonymous methods capture同一variable,所以延迟执行时看到最终value。C 5把foreach iteration variable视为每次iteration在body内新建,closures得到不同storage,使常见直觉成立。
- 3
Caller information attributes
CallerMemberName 、 CallerFilePath 、 CallerLineNumber 标注optional parameters。Call site省略argument时,compiler根据source context写入普通string/int;callee只接收values…
章级决策实验
Chapter 7. C# 5 bonus features:机制与证据
切换《Chapter 7. C# 5 bonus features》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么两个“小Feature”都在修复Hidden Sou…
Closure bug来自reader以为每轮有新value,而compiler曾让delegates共享一个storage;caller info解决callee想知道call site,却不想每处重复脆弱string。两者都把source context变成明确compiler rule:一个调…
可核验证据
以明确的 LangVersion 与目标框架构建「为什么两个“小Feature”都在修复Hidden Sou…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
学完《Chapter 7. C# 5 bonus features》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 7. C# 5 bonus features:失效与核验
为什么两个“小Feature”都在修复Hidden Sou…
典型失效
若解释「为什么两个“小Feature”都在修复Hidden Sou…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「为什么两个“小Feature”都在修复Hidden Sou…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Capturing variables in foreac…
典型失效
若解释「Capturing variables in foreac…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Capturing variables in foreac…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Caller information attributes
典型失效
若解释「Caller information attributes」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Caller information attributes」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
为什么两个“小Feature”都在修复Hidden Source Context
Closure bug来自reader以为每轮有新value,而compiler曾让delegates共享一个storage;caller info解决callee想知道call site,却不想每处重复脆弱string。两者都把source context变成明确compiler rule:一个调整variable scope/lifetime,一个把call-site metadata烤进arguments。
先预测:C# 5修复foreach后for loop capture是否也改变;每轮iteration variable不同是否意味着captured object immutable;CallerFilePath是否runtime stack读取;caller info是否不可伪造。答案都是否。
↡closure保存variable storage而非创建时value snapshot,之后赋值和lifetime由该storage决定。Capturing variables in foreach loops
旧语言语义可理解为iteration variable位于loop外,每次赋新值;所有anonymous methods capture同一variable,所以延迟执行时看到最终value。C# 5把foreach iteration variable视为每次iteration在body内新建,closures得到不同storage,使常见直觉成立。
var actions = new List<Action>();
foreach (var value in new[] { "a", "b", "c" })
{
actions.Add(() => Console.WriteLine(value));
}
// C# 5+: a, b, cThe fix is deliberately narrow
for (var i=0; ...)的i仍是一个loop variable,capture后通常看到loop结束值;需要body-local copy。Foreach修的是iteration-variable declaration semantics,不是“所有loop capture按值”。Code review必须识别实际variable declaration scope。
即使每轮variable storage不同,variable若保存mutable object reference,closures仍看到object后续mutation。想要snapshot应capture immutable value、clone owned data或提前project;“不同reference variable”不保证“不同object state”。
Version and compatibility
同一source用旧/new compiler可能产生不同observable behavior,这正是language breaking change的罕见例子,因旧行为几乎总是bug。Legacy binary不因runtime升级重写closure;只有recompile按新compiler semantics生成。Migration测试要区分existing binary与rebuilt source。
↡C# 5让foreach每轮iteration variable拥有独立storage,从而让每个closure保留对应iteration value。切换旧foreach、新foreach、for与mutable item
Caller information attributes
CallerMemberName、CallerFilePath、CallerLineNumber标注optional parameters。Call site省略argument时,compiler根据source context写入普通string/int;callee只接收values,不inspect stack。Explicit argument优先,因此它们是convenience diagnostics,不是tamper-proof identity。
static void Trace(
string message,
[CallerMemberName] string member = "",
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) { }Member name适合INotifyPropertyChanged helper,减少rename后string drift;file/line可辅助logging,但absolute source path可能泄露build machine/目录并增加cardinality。Release pipeline应normalize/redact,需要stable event schema时用explicit event id而不是source line。
Forwarding wrapper若希望保留original caller,自己也要暴露caller-info optional parameters并显式传给inner method;否则inner compiler填的是wrapper member/file/line。每增加一层都测试provenance,避免日志看似精确却指向helper。
↡caller compiler在省略optional argument时,把member/file/line等source context写成普通argument的机制。切换member、file、line与modern expression
Diagnostic provenance的正确边界
Property notification关心的是property contract;caller member name在property setter helper里合适,但若一个method代表多个properties,应显式传name。Logging关心event identity与operation context;source location只是补充。Guard message可在现代版本用CallerArgumentExpression显示source expression,但它属于C# 10 update,不是第4版C# 5内容。
Caller info values会bake进caller binary:source移动line或rename member后,旧binary仍携带旧values,新build产生新values。Aggregating metrics不应以line/file为长期key。Symbol/PDB与runtime stack各解决其他diagnostic问题,不能互相替代。
void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));切换property、logging、guard与forwarding
本章回顾:Compiler Context是便利证据,不是权威事实
- C# 5 foreach每轮创建独立iteration-variable storage,修复常见closure结果。
- For loop variable未改变,captured object也仍可能mutable;必须分开storage与object state。
- Caller info由caller compiler写入optional arguments,不是runtime stack inspection。
- Wrapper需显式forward original context,file path要privacy/cardinality治理。
- Caller metadata适合diagnostics和notification,不适合security identity或stable event key。
练习
问题 1:为何C# 5 foreach输出正确,for loop仍可能全输出最终index?
问题 2:CallerFilePath可以直接作为日志聚合维度吗?
问题 3:两层logging wrapper怎样保留最外层caller?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- captured storage model
- per-iteration variable semantics
- compiler-emitted caller argument
- diagnostic provenance
- non-authoritative source metadata