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

    为什么Concise Code仍要暴露Timing、Typ…

    本章特性看起来彼此零散,统一主线却很明确:把已经存在的意图放到更接近使用点的位置。Local function把helper贴近owner method,out variable把declaration贴近Try call,throw/default把statement级意图带进expression…

  2. 2

    Local methods

    Local function声明在method body内部,可capture enclosing locals、递归、使用generic type parameters,并拥有普通method-like parameter/return syntax。相较lambda,它不一定需要delegate…

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

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

分步1 / 3

切换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提供意义。

Private protected access (C# 7.2)

private protected表示“同一assembly内的derived types”可访问,是protectedinternal条件的intersection。protected internal则更接近union:same assembly或derived access。两个词序相似但boundary相反,设计framework hooks时要用consumer matrix验证。

Minor improvements in C# 7.3

C# 7.3继续补齐小边界,例如更丰富的generic constraints(unmanagedSystem.EnumSystem.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本来就支持”。

分步1 / 3

切换named、nontrailing、private protected与7.3

本章回顾:把省略掉的信息补进Review与Tests

  1. Local functions可隔离implementation并恢复eager validation,但要显式分析deferred execution。
  2. Out variables贴近call,却不代表result总有效;scope和success condition仍需清楚。
  3. Numeric separators改善阅读,不提供unit或range safety。
  4. Throw/default expressions依赖failure policy与target typing,ambiguous context需恢复显式type。
  5. 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、分配计数或地址/修改轨迹核对复制、别名与边界。

资料与写作方式声明

本章以C# in Depth, Fourth Edition, Chapter 14: Concise code in C# 7权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…