C#

Lesson 05

Using declarations and disposal

Use using or await using to bind disposable resources to scope instead of relying on manual cleanup.

Good Code

src/ReviewExport.cs
public async Task ExportAsync(
    string path,
    IReadOnlyCollection<ReviewDto> reviews,
    CancellationToken cancellationToken)
{
    await using FileStream file = File.Create(path);
    await using var writer = new StreamWriter(file);

    // await using disposes the writer and stream on every exit path.
    foreach (ReviewDto review in reviews)
    {
        await writer.WriteLineAsync(review.Title.AsMemory(), cancellationToken);
    }
}

Bad Code

ReviewExport.cs
public async Task ExportAsync(string path, IReadOnlyCollection<ReviewDto> reviews)
{
    var file = File.Create(path);
    var writer = new StreamWriter(file);

    foreach (ReviewDto review in reviews)
    {
        // An exception skips both Dispose calls below.
        await writer.WriteLineAsync(review.Title);
    }

    writer.Dispose();
    file.Dispose();
}

Review Notes

What to review

Good Code

The good version ties the file stream and writer to scope with await using. Disposal still runs if a write throws.

Bad Code

The bad version calls Dispose manually after the loop. Any exception before those calls leaves resources open until finalization.

Takeaways

  • Disposable resources should have a scope-owned lifetime so exceptions and early returns still release them.