Chapter 2: .NET Resource Management (Items 11-17)

完整覆盖第3版Item 11-17:GC与resource ownership、成员/静态初始化、去重构造逻辑、allocation、构造期virtual dispatch和标准Dispose模式。

学习目标

  • 能区分GC managed-memory reclamation与file/socket/native handle的deterministic release,并写出每个resource的owner
  • 能设计member、constructor、static与lazy initialization的单一truth path,验证所有public construction entries保持invariant
  • 能分析constructor virtual dispatch、unnecessary allocation和错误Dispose/finalizer模式,并以phase与ownership修正

为什么Resource问题首先是Ownership问题

GC回答“某块managed memory是否仍reachable”,不回答“file handle何时关闭”“谁归还pool lease”或“borrowed service能否被当前对象释放”。因此本章把allocation、initialization和cleanup看作同一条lifecycle:创建前定义valid state,使用时保持owner,结束时由owner确定性释放。

先预测:GC最终会回收对象,是否意味着file handle可以不Dispose;class保存一个IDisposable field,是否必然拥有它;有Dispose是否必然需要finalizer?三个答案都是否。

Managed Memory与Deterministic Resource(Item 11)

Item 11: Understand .NET Resource Management

GC通过reachability回收managed memory并压缩部分heap;非托管handle、socket lease、subscription和pooled buffer不由GC按业务时机释放。不要以GC.Collect()作为正常资源管理,也不要仅因对象大就随意置null;先缩短真实reference lifetime并用memory/profile evidence验证。

Owned IDisposable field应由container级联释放;borrowed dependency由外部scope拥有,当前类型不得擅自Dispose。这个区分比“字段是否实现IDisposable”更重要。

分步1 / 3

切换managed、owned、borrowed、native与pooled资源

Initialization只有一个Source of Truth(Items 12-14)

Item 12: Prefer Member Initializers to Assignment Statements

与constructor参数无关、每条construction path相同的safe default放在field/property initializer,减少overload漏赋值。Required domain state仍由constructor/required member验证;不要为了追求initializer把I/O或复杂failure藏到field expression。

Item 13: Use Proper Initialization for Static Class Members

简单static value用inline initializer;必须顺序化、校验或捕获runtime condition时用static constructor,但它最多运行一次,失败会让type在当前runtime context持续不可用。External I/O与可重试startup更适合host composition或Lazy<T>配显式failure policy。

Item 14: Minimize Duplicate Initialization Logic

让public constructors链到一个canonical constructor,或让factory调用同一private creation path。重复assignment会在新增field时产生“某个overload忘了初始化”的silent invalid state。现代primary constructor、required/init member可减少样板,但仍需一处定义invariant。

public sealed class Report
{
    private readonly List<Line> _lines = [];
    public Report(string name) : this(name, SystemClock.Instance) { }
 
    internal Report(string name, IClock clock)
    {
        Name = string.IsNullOrWhiteSpace(name)
            ? throw new ArgumentException("A name is required.", nameof(name))
            : name;
        CreatedAt = clock.UtcNow;
    }
}
分步1 / 3

切换member、constructor、static、lazy与duplicate路径

Allocation、Construction与Cleanup(Items 15-17)

Item 15: Avoid Creating Unnecessary Objects

先以allocation profile确认hot path,再减少temporary string、closure、boxing或重复materialization。Cache只适合immutable或有清晰eviction/tenant boundary的结果,pool只适合创建昂贵且ownership可严格归还的resource;无界cache与错误pool比原allocation更危险。

byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
try { ReadFrame(buffer); }
finally { ArrayPool<byte>.Shared.Return(buffer, clearArray: true); }

Item 16: Never Call Virtual Functions in Constructors

Base constructor运行时derived fields尚未完成初始化,virtual call却可dispatch到derived override,暴露partial object。Constructor只建立自身invariant;需要polymorphic activation时,使用factory在完整构造后调用显式Start,或把variation作为constructor collaborator注入。

Item 17: Implement the Standard Dispose Pattern

Sealed type拥有managed disposable resource时通常实现简单、幂等的Dispose并级联owner fields。可继承type使用public nonvirtual Dispose()加protected virtual Dispose(bool);直接native handle优先交给SafeHandle,避免自己编写脆弱finalizer。Async cleanup用IAsyncDisposable,但不要让Dispose承担必须成功的business commit。

public sealed class ExportSession(Stream output) : IDisposable
{
    private Stream? _output = output;
 
    public void Dispose()
    {
        Interlocked.Exchange(ref _output, null)?.Dispose();
    }
}
分步1 / 3

切换constructor、factory、allocation与dispose案例

本章回顾:从创建到释放只有一条可证明的Lifecycle

  1. GC回收managed memory;resource owner负责scarce/native/leased resource的及时释放。
  2. Member initializer承载共同safe default,canonical constructor承载参数validation,static I/O移到hosted composition。
  3. 所有public construction entries必须建立同一invariant并具有明确失败语义。
  4. Allocation optimization需要profile;cache/pool同时引入lifetime与eviction contract。
  5. Constructor不得virtual dispatch;对象完整后再activate。
  6. Dispose按ownership级联并幂等,native fallback优先SafeHandle。

练习

问题 1:一个类接收DI提供的HttpClient并自行创建FileStream,应如何释放?

问题 2:五个constructor重复设置defaults,static constructor还访问远程配置,如何重构?

问题 3:可继承类型直接持有native handle并在constructor调用virtual Start,有哪些问题?

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

resource owner
canonical construction path
partial object
idempotent disposal
SafeHandle boundary

讨论

评论区加载中…