Chapter 14. Concise code in C# 7
覆盖local methods、out variables、numeric literals、throw expressions、default literals、C# 7.2 named arguments/private protected及C# 7.3小特性。
学习目标
- 能设计local function与out variable的scope和execution timing,区分eager validation、deferred work与definite assignment
- 能解释numeric separators、throw expression和default literal压缩了什么type/failure context,并写出消除ambiguity的版本
- 能分析named arguments、private protected与C# 7.3 minor features对source contract、accessibility和LangVersion matrix的影响
机制总览
Chapter 14. Concise code in C# 7:机制路径
- 1
为什么Concise Code仍要暴露Timing、Typ…
本章特性看起来彼此零散,统一主线却很明确:把已经存在的意图放到更接近使用点的位置。Local function把helper贴近owner method,out variable把declaration贴近Try call,throw/default把statement级意图带进expression…
- 2
Local methods
Local function声明在method body内部,可capture enclosing locals、递归、使用generic type parameters,并拥有普通method-like parameter/return syntax。相较lambda,它不一定需要delegate…
- 3
Out variables
C 7允许在argument位置声明 out int value ,类型也可用 out var value 推断。变量scope通常属于enclosing block,而不只是if true branch;但definite assignment和business validity仍由control…
章级决策实验
Chapter 14. Concise code in C# 7:机制与证据
切换《Chapter 14. Concise code in C# 7》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么Concise Code仍要暴露Timing、Typ…
本章特性看起来彼此零散,统一主线却很明确:把已经存在的意图放到更接近使用点的位置。Local function把helper贴近owner method,out variable把declaration贴近Try call,throw/default把statement级意图带进expression…
可核验证据
以明确的 LangVersion 与目标框架构建「为什么Concise Code仍要暴露Timing、Typ…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
学完《Chapter 14. Concise code in C# 7》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 14. Concise code in C# 7:失效与核验
为什么Concise Code仍要暴露Timing、Typ…
典型失效
若解释「为什么Concise Code仍要暴露Timing、Typ…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「为什么Concise Code仍要暴露Timing、Typ…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Local methods
典型失效
若解释「Local methods」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Local methods」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Out variables
典型失效
若解释「Out variables」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Out variables」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
为什么Concise Code仍要暴露Timing、Type与API Contract
本章特性看起来彼此零散,统一主线却很明确:把已经存在的意图放到更接近使用点的位置。Local function把helper贴近owner method,out variable把declaration贴近Try call,throw/default把statement级意图带进expression,named arguments把parameter meaning带到call site。每次缩短都要检查被隐藏的execution timing、target type和source compatibility。
先预测:iterator local function中的validation是否在outer method被调用时执行;out variable是否只活在if true branch;digit separator是否影响literal value;default是否在任何overload中都能推断;parameter rename是否对使用named argument的source consumer无影响;private protected是否等于protected或internal。答案都不能靠“看起来如此”判断。
Local methods
Local function声明在method body内部,可capture enclosing locals、递归、使用generic type parameters,并拥有普通method-like parameter/return syntax。相较lambda,它不一定需要delegate conversion,名称和recursive call更自然;相较private helper,它的visibility与concept ownership限制在一个method内。
最重要的timing pattern是wrapper先validate,再返回local iterator或Task。若validation直接写在iterator body中,调用method只创建enumerable,异常要到第一次MoveNext才发生;拆成outer validation + local iterator,可让bad argument在API call时立刻失败。
static IEnumerable<string> ReadLines(string path)
{
if (path is null) throw new ArgumentNullException(nameof(path));
return Iterator();
IEnumerable<string> Iterator()
{
using var reader = File.OpenText(path);
while (reader.ReadLine() is { } line) yield return line;
}
}切换local eager、lambda、out variable与discard
Out variables
C# 7允许在argument位置声明out int value,类型也可用out var value推断。变量scope通常属于enclosing block,而不只是if true branch;但definite assignment和business validity仍由control flow决定。TryParse返回false时out variable也会被赋default,这不意味着它是有效parse result。
Discard out _明确忽略output,适合只关心success的call。大量out parameters通常暴露API shape问题;tuple或nominal result能更清楚表达相关outputs和invalid states。Concision不应让scope比需要的更宽,必要时增加block缩小lifetime。
if (int.TryParse(text, out int count) && count >= 0)
{
Use(count);
}
bool isGuid = Guid.TryParse(candidate, out _);Improvements to numeric literals
C# 7加入binary literals 0b...和digit separators _,让bit masks、large quantities和grouped identifiers更可读。Separators不改变numeric value或type inference;suffix、range和checked context仍决定literal type与overflow。Grouping应按domain,而不是机械每三位:bit fields常按4或8位分组,money/IDs有自己的规则。
Literal readability需要与runtime unit配对。60_000若代表milliseconds,应通过name/type表达单位;separator只帮助视觉扫描,不能创建unit safety。
Throw expressions
C# 7允许在部分expression contexts使用throw,例如null-coalescing右侧、conditional arm或expression-bodied member。它把invalid input policy放在value construction附近,适合constructor assignment和guarded mapping。Exception type、message/context与ownership仍要设计,短syntax不是随意抛generic Exception的许可。
public Customer(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
public string DisplayName =>
profile?.Name ?? throw new InvalidOperationException("Profile has no name.");Default literals (C# 7.1)
default literal从target type获得含义,替代重复的default(T)、default(CancellationToken)。在assignment、return和typed argument中通常清楚;若overload resolution缺少唯一target,仍需写出default(Type)或cast。对于reference/value nullable semantics,default只产生type的零值,不保证domain-valid。
切换throw、default、numeric与tuple comparison
Nontrailing named arguments (C# 7.2)
早期named arguments后通常要求后续也named;C# 7.2允许named argument出现在正确position时,后面继续positional arguments。它适合突出一个不明显的boolean/quantity,但混合风格过多会降低扫描性。Name必须对应parameter,参数重命名可能破坏source recompilation,即使CLR signature不含parameter name作为调用identity。
Named arguments是source-level dependency。Public library要把parameter names纳入compatibility review;callers也不应把每个显然位置都重复命名。优先为容易混淆或optional values提供意义。
↡Caller把parameter name写入source后形成的可读性收益与rename compatibility依赖。Private protected access (C# 7.2)
private protected表示“同一assembly内的derived types”可访问,是protected与internal条件的intersection。protected internal则更接近union:same assembly或derived access。两个词序相似但boundary相反,设计framework hooks时要用consumer matrix验证。
Minor improvements in C# 7.3
C# 7.3继续补齐小边界,例如更丰富的generic constraints(unmanaged、System.Enum、System.Delegate)、tuple equality、stackalloc initializer和部分overload/ref场景改进。它们必须和project LangVersion、compiler SDK及runtime/library availability分开验证;syntax来自compiler,某些types/APIs来自target framework。
第四版以当时C# 7.3为已发布背景。Modern compiler可能拥有更多相似特性,维护教材时应保留每项first-version label,不能把“现在可编译”误写成“C# 7本来就支持”。
切换named、nontrailing、private protected与7.3
本章回顾:把省略掉的信息补进Review与Tests
- Local functions可隔离implementation并恢复eager validation,但要显式分析deferred execution。
- Out variables贴近call,却不代表result总有效;scope和success condition仍需清楚。
- Numeric separators改善阅读,不提供unit或range safety。
- Throw/default expressions依赖failure policy与target typing,ambiguous context需恢复显式type。
- Named arguments、accessibility和minor versions都是source/build contracts,而非纯排版糖。
练习
问题 1:一个返回IEnumerable的API为什么在null参数时没有立即抛错?
问题 2:Public method改parameter name前如何评估影响?
问题 3:怎样证明private protected满足插件hook边界?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- eager validation boundary
- literal intent
- expression failure path
- target-typed default
- call-site naming contract
- accessibility intersection
原版目录概念补充核对
以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。
Improvements to numeric literals:机制、边界与证据
Chapter 14. Concise code in C# 7中的Improvements to numeric literals必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Default literals (C# 7.1):机制、边界与证据
Chapter 14. Concise code in C# 7中的Default literals (C# 7.1)必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Nontrailing named arguments (C# 7.2):机制、边界与证据
Chapter 14. Concise code in C# 7中的Nontrailing named arguments (C# 7.2)涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Private protected access (C# 7.2):机制、边界与证据
Chapter 14. Concise code in C# 7中的Private protected access (C# 7.2)必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Minor improvements in C# 7.3:机制、边界与证据
Chapter 14. Concise code in C# 7中的Minor improvements in C# 7.3涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。