Chapter 1: C# Language Idioms (Items 1-10)
完整覆盖第3版Item 1-10:局部类型推断、readonly、pattern/cast、字符串与文化、强类型API、delegate/event、装箱及new成员隐藏。
学习目标
- 能判断可读性、静态类型、版本边界和culture ownership,选择var、readonly、pattern、interpolation与FormattableString
- 能设计compiler可检查且lifetime明确的command、callback和event API contract
- 能分析boxing allocation与new member hiding,并用generic或virtual dispatch及针对性测试修正
为什么语言习惯必须让类型表达决策
本章的10条建议并非追求短语法,而是让source code保留足够的type、culture、ownership与dispatch信息。var可以减少重复,interpolation可以让template贴近value,但只要它们遮住numeric width、cross-culture contract或runtime allocation,就应改用更明确的表达。
先预测:把所有local改成var、把所有public value改成const、把所有type mismatch改成as,是否仍能保留相同的版本与失败contract?逐项答案都是否。
Type、Value与Text边界(Items 1-5)
Item 1: Prefer Implicitly Typed Local Variables
右侧构造、LINQ projection或anonymous type已经清楚给出类型时用var,避免把复杂generic type重复两遍。若method name没有揭示返回的precision、ownership或abstraction,显式类型更利于review。验收不是“是否全部var”,而是reader能否在当前行推断contract。
Item 2: Prefer readonly to const
const值会编译进consumer,public library改值后旧consumer仍使用旧literal;static readonly在runtime从定义assembly读取,适合可能演进的domain value。真正跨版本永不变化且需要constant expression的值才用const。
public static class Limits
{
public static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(5);
public const int BytesPerKilobyte = 1024;
}Item 3: Prefer the is or as Operators to Casts
当mismatch是预期branch时,declaration pattern同时测试和bind:if (message is Invoice invoice)。当contract保证类型必须匹配时,explicit cast可fail fast。as只适用于reference/nullable并把mismatch变成null,必须避免与“合法null”混淆。
Item 4: Replace string.Format() with Interpolated Strings
Interpolation把placeholder与expression并置,重排参数时不易错;但它不会自动解决logging template、localization或invariant serialization。Structured logging应保留property names,machine protocol应使用stable serializer,而非把任何文本都插值化。
Item 5: Prefer FormattableString for Culture-Specific Strings
FormattableString保留composite format与typed arguments,让最终sink选择culture。UI通常在边界使用用户culture;signature、cache key、wire value则显式invariant。不要先产生普通string再尝试恢复丢失的culture intent。
切换五种expression与formatting场景
Strong Contract与Invocation边界(Items 6-8)
Item 6: Avoid String-ly Typed APIs
用string传command、member name或state会把拼写错误推迟到runtime。优先enum、value object、generic type、expression或具体command;若external protocol只能是string,在adapter处parse/validate并尽快转成domain type。
Item 7: Express Callbacks with Delegates
Delegate同时声明参数、返回值和同步调用形态,比“method name string + reflection”可检查。选择Action/Func用于通用签名,domain语义强或需文档时声明named delegate。还要说明callback是否可重入、是否允许抛异常、在哪个thread调用。
Item 8: Use the Null Conditional Operator for Event Invocations
Changed?.Invoke(this, args)只读取一次event delegate,并清楚表达“无subscriber则不调用”。这不等于完整event contract:publisher仍需确定thread、subscriber exception、ordering和unsubscribe;跨thread修改subscription时还要按实际concurrency model验证。
public event EventHandler<PriceChangedEventArgs>? PriceChanged;
private void RaisePriceChanged(decimal before, decimal after) =>
PriceChanged?.Invoke(this, new(before, after));比较string command、delegate、event和raise
Runtime Cost与Inheritance演进(Items 9-10)
Item 9: Minimize Boxing and Unboxing
Value type转成object或某些interface representation时会boxing并可能分配;unboxing要求exact boxed type。优先generic collection/API保留T,hot path再用allocation profiler验证。不要为消除一次冷路径boxing制造复杂unsafe design。
Item 10: Use the new Modifier Only to React to Base Class Updates
new不是polymorphic override,它明确隐藏base同名member,调用目标取决于reference的compile-time type。典型合法场景是base library升级后新增同名member,而derived type必须保留既有semantics;仍应审计全部call sites。需要substitutability时应由base virtual、derived override建立同一slot。
Base value = new Derived();
value.Render(); // override: Derived.Render; new: Base.Render切换boxing与dispatch案例
本章回顾:语法选择必须留下可验证的Contract
var保留static type,但可读性由当前表达式是否揭示contract决定。readonly避免public const的literal baking;culture由最终格式化边界显式拥有。- Pattern表达预期shape branch,explicit cast表达contract violation。
- Type、delegate和event把字符串协议升级为compiler proof,但仍需lifetime和thread policy。
- Generic API减少boxing;
new只明确隐藏,不替代override。
练习
问题 1:library公开timeout值,UI和签名日志分别如何选择readonly与culture?
问题 2:怎样把Run("price", callbackName)改成可验收的强类型API?
问题 3:何时修boxing,何时用new,分别需要什么证据?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- local type inference
- culture ownership
- stringly typed API
- boxing
- member hiding