Chapter 13. Improving efficiency with more pass by reference
覆盖ref回顾、ref locals/returns、in、readonly structs、ref/in extensions与ref-like structs,以alias、copy、lifetime和measurement证明优化。
学习目标
- 能解释value copy与managed alias的差异,推导ref local/ref return的write-through behavior和lifetime限制
- 能分析in parameter、readonly struct/member与defensive copy的关系,用benchmark和generated-code evidence验证真实收益
- 能设计ref/in extension与ref-like struct API,写出escape、async、boxing、storage和version boundaries的验收矩阵
机制总览
Chapter 13. Improving efficiency with more pass by reference:机制路径
- 1
为什么Pass by Reference首先是正确性与Li…
“避免复制大struct”只是ref features的一种用途。 ref 改变的是expression是否代表一个value还是一个storage location的alias;一旦alias可写,修改会穿透到original storage,一旦alias逃逸,referent lifetime…
- 2
Recap: What do you know about…
C 早期的 ref / out parameters就允许callee访问caller variable storage。
- 3
Ref locals and ref returns
Ref local保存alias,ref return把callee选中的storage alias交给caller。Return expression必须有足够escape scope,例如array element或对象field可能安全,普通stack local不安全。Caller可选择按v…
章级决策实验
Chapter 13. Improving efficiency with more pass by reference:机制与证据
切换《Chapter 13. Improving efficiency with more pass by reference》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么Pass by Reference首先是正确性与Li…
“避免复制大struct”只是ref features的一种用途。 ref 改变的是expression是否代表一个value还是一个storage location的alias;一旦alias可写,修改会穿透到original storage,一旦alias逃逸,referent lifetime…
可核验证据
以明确的 LangVersion 与目标框架构建「为什么Pass by Reference首先是正确性与Li…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
学完《Chapter 13. Improving efficiency with more pass by reference》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 13. Improving efficiency with more pass by reference:失效与核验
为什么Pass by Reference首先是正确性与Li…
典型失效
若解释「为什么Pass by Reference首先是正确性与Li…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「为什么Pass by Reference首先是正确性与Li…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Recap: What do you know about…
典型失效
若解释「Recap: What do you know about…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Recap: What do you know about…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Ref locals and ref returns
典型失效
若解释「Ref locals and ref returns」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Ref locals and ref returns」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
为什么Pass by Reference首先是正确性与Lifetime问题
“避免复制大struct”只是ref features的一种用途。ref改变的是expression是否代表一个value还是一个storage location的alias;一旦alias可写,修改会穿透到original storage,一旦alias逃逸,referent lifetime必须足够长。Performance优化建立在这套correctness proof之上,不能反过来用“更快”跳过alias与escape分析。
先预测:property能否作为任意ref argument;ref return能否返回普通local;in是否保证call-site零复制;readonly field调用mutable struct method是否可能产生defensive copy;Span的backing array在heap上是否就允许Span本身存进class field。答案分别是“通常不能、不能、不能保证、可能、不能”。
Recap: What do you know about ref?
C#早期的ref/out parameters就允许callee访问caller variable storage。ref要求调用前definitely assigned且可读写;out允许未初始化但callee必须赋值。Argument必须是可按引用传递的variable,普通property调用是method access而不是stable variable location,所以通常不能直接作为ref argument。
Value assignment x = y复制当前value;ref assignment x = ref y让x指向y的storage。两者在syntax与review上必须区分。Aliasing还会影响线程安全和invariants:绕过owner method直接write-through可能让validation、notification或locking失效。
Ref locals and ref returns
Ref local保存alias,ref return把callee选中的storage alias交给caller。Return expression必须有足够escape scope,例如array element或对象field可能安全,普通stack local不安全。Caller可选择按value接收copy,也可用ref保持alias;API contract必须说明可写性与storage invalidation。
static ref int FindSlot(int[] values, int index) => ref values[index];
ref int slot = ref FindSlot(buffer, 2);
slot = 99; // writes buffer[2]
int snapshot = FindSlot(buffer, 2); // copies the current value切换value、ref local、ref return与ref readonly
in parameters (C# 7.2)
in parameter声明callee只读访问argument,并允许按reference传递以减少large struct copy。Call-site的in modifier可省略,因此compiler可能为rvalue、conversion result或property value创建temporary。它表达的是callee view readonly,不是“编译器保证绝无任何copy”。
即使parameter本身readonly,调用non-readonly instance member时也可能复制receiver以防method修改original。优化前应看struct size、call frequency、JIT behavior与member declarations。Small structs by value可能更快、更易inline;API加in还会影响overload/signature和compatibility。
static decimal Total(in PriceQuote quote) => quote.Price * quote.Quantity;
var total1 = Total(in quote); // explicit variable alias
var total2 = Total(CreateQuote()); // a temporary may be introduced切换by value、in variable、in expression与readonly
Declaring structs as readonly (C# 7.2)
readonly struct承诺实例fields不可在构造后被修改,instance fields需readonly,properties通常只读。这让compiler和reader知道instance methods不应修改state,并减少从readonly storage访问时的defensive copies。它不使referenced objects immutable:readonly struct内的List reference仍可指向mutable list。
Modern C#还允许标记individual readonly members,这是本书之后的补充;第四版本章核心是C# 7.2 readonly struct与in配合。Public mutable struct本身容易产生copy-and-mutate confusion,若value semantics成立,优先让整个type immutable/readonly。
↡从readonly variable、field或in parameter调用non-readonly struct member时,receiver为保护原值可能被复制。Extension methods with ref or in parameters (C# 7.2)
Extension method的first parameter可用ref或in修饰特定struct receiver,使counter.Increment()能够write-through,或让large readonly value以alias访问。它把mutation包装成method-call外观,因此API命名必须强烈表达修改,例如Advance、NormalizeInPlace,不能用像query的名字隐藏write。
Generic、reference-type receiver与language version对ref/in extensions有约束。Library发布前应验证目标LangVersion、metadata signature和普通instance methods的冲突。Extension lookup convenience不能替代owner type的invariant control。
Ref-like structs (C# 7.2)
ref struct用于只能安全存在于stack-like scope的values,典型是Span<T>/ReadOnlySpan<T>这样的memory views。Compiler限制boxing、ordinary class fields、array elements、capture以及C# 7.2时代跨await/yield使用,以防view逃出referent lifetime或被搬到heap object。
static int Sum(ReadOnlySpan<int> values)
{
int total = 0;
foreach (int value in values) total += value;
return total;
}
Span<int> scratch = stackalloc int[16];Ref-like不等于backing storage一定在stack:Span可view array、native memory或stackalloc。限制针对view value和lifetime proof。后续C#版本逐步放宽某些场景,但每次放宽仍受ref safety规则约束;学习本章时以C# 7.2 baseline为准,现代项目再按实际compiler文档验证。
↡Compiler计算一个ref或ref-like value可以安全被返回、赋值或捕获到多大范围的lifetime上界。 ↡类型不能被box或存入普通heap locations,并受capture、async和iterator限制的lifetime contract。切换array、stackalloc、field与async capture
本章回顾:Alias Proof先于Performance Claim
- Ref local/return代表storage alias,value local代表snapshot copy,mutation semantics完全不同。
- In提供readonly by-reference intent,但call-site temporary和defensive copy仍需分析。
- Readonly struct帮助表达immutable value semantics,并给compiler更强的copy信息。
- Ref/in extensions可把alias行为包装为instance syntax,命名与invariant ownership必须清楚。
- Ref-like structs以compile-time restrictions维护lifetime safety;优化必须由measurement证明。
练习
问题 1:一个API返回List内部元素的writable ref,应先问什么?
问题 2:把32-byte struct parameter改成in后怎样证明收益?
问题 3:设计stackalloc parser时如何证明Span没有逃逸?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- managed alias
- defensive copy
- readonly receiver boundary
- ref escape scope
- stack-only contract
原版目录概念补充核对
以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。
Ref locals and ref returns:机制、边界与证据
Chapter 13. Improving efficiency with more pass by reference中的Ref locals and ref returns涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
in parameters (C# 7.2):机制、边界与证据
Chapter 13. Improving efficiency with more pass by reference中的in parameters (C# 7.2)涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Declaring structs as readonly (C# 7.2):机制、边界与证据
Chapter 13. Improving efficiency with more pass by reference中的Declaring structs as readonly (C# 7.2)涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Extension methods with ref or in parameters (C# 7.2):机制、边界与证据
Chapter 13. Improving efficiency with more pass by reference中的Extension methods with ref or in parameters (C# 7.2)涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Ref-like structs (C# 7.2):机制、边界与证据
Chapter 13. Improving efficiency with more pass by reference中的Ref-like structs (C# 7.2)必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。