The garbage collector reclaims memory and nothing else. These are the decisions that make cleanup deterministic: which resources a using covers, which type owns each disposable, and what a finalizer is still allowed to do when nobody calls Dispose.
A `using` declaration replaces the hand-written `try/finally`IDE0063
The using statement and the using declaration compile to the same try/finally, so Dispose runs when the block exits by exception and when it exits by return. The declaration drops the braces and ties the lifetime to the enclosing scope; several instances declared in one statement are disposed in reverse order of declaration.
Preferred
using var reader = new StreamReader(path);
return reader.ReadToEnd();Avoid
var reader = new StreamReader(path);
var text = reader.ReadToEnd();
reader.Dispose();Corpus: using statement · Using objects that implement IDisposable · IDE0063: Use simple 'using' statement
Build a returned disposable outside the block that cleans it upCA2000
A using block disposes its resource before the caller can see it, so a method that returns a disposable finishes every step that can fail before it moves the instance to the value it returns. The temporary stays in a try/finally that releases it on the paths that never reach the return.
Preferred
var temp = new SerialPort(name);
SerialPort port = null;
try { temp.Open(); SomeWork(); port = temp; temp = null; }
finally { temp?.Dispose(); }
return port;Avoid
var port = new SerialPort(name);
port.Open();
SomeWork();
return port;Corpus: CA2000: Dispose objects before losing scope · Using objects that implement IDisposable
A type that owns a disposable field implements `IDisposable`CA1001
A class that declares an IDisposable field owns an unmanaged resource indirectly, so the class implements IDisposable and disposes the field from its own Dispose. It does not implement a finalizer: nothing unmanaged is held directly, and the field brings its own finalization with it.
Preferred
sealed class Log : IDisposable
{
private readonly StreamWriter _writer = new("app.log");
public void Dispose() => _writer.Dispose();
}Avoid
sealed class Log
{
private readonly StreamWriter _writer = new("app.log");
}Corpus: CA1001: Types that own disposable fields should be disposable · Implement a Dispose method
Dispose only what you ownCA2213
Ownership decides who disposes. An instance the type created is the type's to release, while one that arrived through a parameter belongs to its creator and is left alone, so a class holding a caller's object does not become disposable on its account. Disposing on both sides releases the same resource twice.
Preferred
sealed class Api(HttpClient client)
{
public Task<string> GetAsync(string url) => client.GetStringAsync(url);
}Avoid
sealed class Api(HttpClient client) : IDisposable
{
public void Dispose() => client.Dispose();
}Corpus: CA2213: Disposable fields should be disposed · Implement a Dispose method
`Dispose()` delegates to `Dispose(bool)`CA1063
Any unsealed type that implements IDisposable adds a protected virtual void Dispose(bool disposing) overload: the public method calls it with true, and a finalizer calls it with false. A derived type overrides that overload, releases the state it added, and passes disposing to base.Dispose(disposing) so the base releases what it owns.
Preferred
public void Dispose() => Dispose(true);
protected virtual void Dispose(bool disposing)
{
if (disposing) _inner.Dispose();
}Avoid
class Derived : Base, IDisposable
{
public void Dispose() => _extra.Dispose();
}Corpus: CA1063: Implement IDisposable correctly · CA2215: Dispose methods should call base class dispose · Cleaning up unmanaged resources
Suppress finalization from `Dispose`CA1816
Once Dispose has released the resources, the finalizer has nothing left to do, so the method calls GC.SuppressFinalize(this) for the object it just cleaned up. An unsealed type makes the call even when it has no finalizer, so a derived type that adds one does not have to reimplement IDisposable to reach the same suppression.
Preferred
GC.SuppressFinalize(this);Avoid
GC.SuppressFinalize(_inner);Corpus: CA1816: Call GC.SuppressFinalize correctly · Implement a Dispose method
A finalizer only for a handle you own directly
Write a finalizer only for a type that holds an unmanaged resource directly, and wrap that handle in a SafeHandle so the finalizer, the once-only release, and the synchronization come with it. Finalization runs at a moment the GC chooses and is not called at process exit on .NET 5 and later, so Dispose stays the reliable path; an empty finalizer only adds a second collection.
Preferred
private readonly SafeFileHandle _handle;
public void Dispose() => _handle.Dispose();Avoid
private IntPtr _handle;
~Resource() => Marshal.FreeHGlobal(_handle);Corpus: Finalizers · Implement a Dispose method · CA1821: Remove empty finalizers
`await using` for cleanup that must be awaited
Cleanup that performs I/O implements IAsyncDisposable, whose DisposeAsync returns a ValueTask that await using awaits instead of blocking a thread. Implement IDisposable as well when synchronous cleanup is possible: a class that offers only DisposeAsync leaks when a caller disposes it through Dispose, which never reaches the asynchronous path.
Preferred
await using var stream = File.Create(path, 4096, FileOptions.Asynchronous);
await WriteReportAsync(stream, ct);Avoid
var stream = File.Create(path, 4096, FileOptions.Asynchronous);
await WriteReportAsync(stream, ct);`CancellationTokenSource` goes in a `using`
The source implements IDisposable, so it is released when the operation that owns it finishes, and a using declaration around that operation ties the two lifetimes together. The token handed to the operation is a struct, which leaves the source as the only part of the pair with a lifetime to manage.
Preferred
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await RunAsync(cts.Token);Avoid
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await RunAsync(cts.Token);Corpus: Cancellation in Managed Threads
Do not dispose under a task that is still runningCA2025
A using block ends as soon as control leaves it, and a task started inside the block may still be using the instance it was handed. Await the task inside the scope so the instance outlives the work, or give the task a resource it owns.
Preferred
using var stream = File.OpenRead(path);
await HashAsync(stream, ct);Avoid
using (var stream = File.OpenRead(path))
{
return HashAsync(stream, ct);
}Corpus: CA2025: Do not pass 'IDisposable' instances into unawaited tasks · Using objects that implement IDisposable
Throw `ObjectDisposedException` from the helperCA1513
A member that needs live state checks the disposed flag before it touches that state, and the throw helper does it in one call instead of an if block that dominates a small member and keeps it from being inlined.
Preferred
ObjectDisposedException.ThrowIf(_disposed, this);
return _buffer.Length;Avoid
if (_disposed)
throw new ObjectDisposedException(nameof(Buffer));`GC.Collect` does not dispose anything
A collection reclaims memory and knows nothing about Dispose, so forcing one leaves a handle open and a pooled buffer unreturned. The forced mode is a full blocking collection of every generation, and inducing it often costs more performance than it saves.
Preferred
stream.Dispose();Avoid
GC.Collect();Corpus: Induced Collections · Finalizers
Return a rented array to the pool
Rent hands out a buffer the pool expects back, so the matching Return sits in a finally and both calls stay inside one method. The array is borrowed memory: a reference that escapes can be returned early and read again after the pool has handed the same array to another caller.
Preferred
var buffer = ArrayPool<byte>.Shared.Rent(size);
try { ReadInto(buffer); }
finally { ArrayPool<byte>.Shared.Return(buffer); }Avoid
_buffer = ArrayPool<byte>.Shared.Rent(size);
Use(_buffer);
ArrayPool<byte>.Shared.Return(_buffer);Corpus: CA1838: Avoid StringBuilder parameters for P/Invokes · Unsafe code best practices
Pattern-based `using` on a `ref struct`
A ref struct cannot implement an interface, so the using statement and declaration accept the disposable pattern instead: an accessible, parameterless instance Dispose method returning void is enough, and the resource is released at the end of the scope like any other.
Preferred
ref struct Lease
{
public void Dispose() { }
}Avoid
ref struct Lease
{
private void Dispose() { }
}Corpus: using statement