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. 1

    为什么Pass by Reference首先是正确性与Li…

    “避免复制大struct”只是ref features的一种用途。 ref 改变的是expression是否代表一个value还是一个storage location的alias;一旦alias可写,修改会穿透到original storage,一旦alias逃逸,referent lifetime…

  2. 2

    Recap: What do you know about…

    C 早期的 ref / out parameters就允许callee访问caller variable storage。

  3. 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
分步1 / 3

切换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
分步1 / 3

切换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。

Extension methods with ref or in parameters (C# 7.2)

Extension method的first parameter可用refin修饰特定struct receiver,使counter.Increment()能够write-through,或让large readonly value以alias访问。它把mutation包装成method-call外观,因此API命名必须强烈表达修改,例如AdvanceNormalizeInPlace,不能用像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文档验证。

分步1 / 3

切换array、stackalloc、field与async capture

本章回顾:Alias Proof先于Performance Claim

  1. Ref local/return代表storage alias,value local代表snapshot copy,mutation semantics完全不同。
  2. In提供readonly by-reference intent,但call-site temporary和defensive copy仍需分析。
  3. Readonly struct帮助表达immutable value semantics,并给compiler更强的copy信息。
  4. Ref/in extensions可把alias行为包装为instance syntax,命名与invariant ownership必须清楚。
  5. 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 或运行轨迹以及版本对照验证结论。

讨论

评论区加载中…