ReflectionIT.DisposeGenerator 0.4.0-preview

Prefix Reserved
This is a prerelease version of ReflectionIT.DisposeGenerator.
dotnet add package ReflectionIT.DisposeGenerator --version 0.4.0-preview
                    
NuGet\Install-Package ReflectionIT.DisposeGenerator -Version 0.4.0-preview
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ReflectionIT.DisposeGenerator" Version="0.4.0-preview" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ReflectionIT.DisposeGenerator" Version="0.4.0-preview" />
                    
Directory.Packages.props
<PackageReference Include="ReflectionIT.DisposeGenerator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ReflectionIT.DisposeGenerator --version 0.4.0-preview
                    
#r "nuget: ReflectionIT.DisposeGenerator, 0.4.0-preview"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ReflectionIT.DisposeGenerator@0.4.0-preview
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ReflectionIT.DisposeGenerator&version=0.4.0-preview&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ReflectionIT.DisposeGenerator&version=0.4.0-preview&prerelease
                    
Install as a Cake Tool

ReflectionIT.DisposeGenerator

A source generator package that implements the dispose and async dispose patterns.

NuGet package

Package Version
ReflectionIT.DisposeGenerator NuGet

Installation

<PackageReference Include="ReflectionIT.DisposeGenerator" Version="*" />

Quick start

Annotate a partial class or struct with the Disposable attribute and mark disposable fields or properties with Dispose.

using System;
using System.IO;
using ReflectionIT.DisposeGenerator.Attributes;

[Disposable]
public partial class LogWriter : IDisposable {

    [Dispose]
    private readonly StreamWriter _streamWriter;

    public LogWriter(string path) => _streamWriter = new StreamWriter(path);

    public void WriteLine(string text) {
        ThrowIfDisposed();
        _streamWriter.WriteLine($"{DateTime.Now}\t{text}");
    }
}

The generator creates the dispose members for the annotated type, including _isDisposed, an IsDisposed property, and ThrowIfDisposed() by default.

Requirements and diagnostics

  • The annotated type must be partial.
  • [Disposable] can be applied to classes and structs.
  • [Dispose] and [AsyncDispose] can be applied to fields and properties.
  • The generator emits RITDG001 when a type annotated with [Disposable] is not declared partial.
  • Members annotated with [Dispose] or [AsyncDispose] must support the generated dispose call pattern. Otherwise the generated code can produce compiler errors.

RITDG001

RITDG001: Type '{typeName}' is annotated with [Disposable] and must be declared partial for ReflectionIT.DisposeGenerator to generate code.

Attribute reference

DisposableAttribute

Property Default Description
OverrideDispose false Generates Dispose(bool) as an override instead of generating a public Dispose() method.
OverrideDisposeAsyncCore false Generates DisposeAsyncCore() as an override instead of generating a public DisposeAsync() method.
GenerateThrowIfDisposed true Generates ThrowIfDisposed() for guarding public instance members.
ExplicitInterfaceImplementation false Generates explicit IDisposable.Dispose() and IAsyncDisposable.DisposeAsync() implementations when applicable.
IsThreadSafe false Uses thread-safe disposal state transitions via Interlocked.CompareExchange.
HasUnmanagedResources false Adds a finalizer and ReleaseUnmanagedResources() partial method support.

DisposeAttribute

Property Default Description
SetToNull false Sets the annotated field or property to null after disposal.

AsyncDisposeAttribute

Property Default Description
SetToNull false Sets the annotated field or property to null after asynchronous disposal.
ConfigureAwait true Controls the ConfigureAwait(...) value used for generated async disposal calls.

What gets generated

Depending on the options and the annotated members, the generator can create:

  • Dispose()
  • Dispose(bool)
  • DisposeAsync()
  • DisposeAsyncCore()
  • IsDisposed
  • ThrowIfDisposed()
  • _isDisposed
  • a finalizer
  • ReleaseUnmanagedResources()

Call ThrowIfDisposed() at the start of public instance members that depend on resources managed by the generated dispose pattern. The generated method uses the generated IsDisposed property, so derived types can customize disposed-state checks through IsDisposed instead of overriding ThrowIfDisposed().

using ReflectionIT.DisposeGenerator.Attributes;

[Disposable]
public partial class LogWriter : IDisposable {

    [Dispose]
    private readonly StreamWriter _streamWriter;

    public LogWriter(string path) => _streamWriter = new StreamWriter(path);

    public void WriteLine(string text) {
        ThrowIfDisposed();
        _streamWriter.WriteLine($"{DateTime.Now}\t{text}");
    }
}

This helps fail fast with an ObjectDisposedException when the instance is used after it has been disposed.

SetToNull usage

Use SetToNull when the property or field should be set to null after disposal.

using ReflectionIT.DisposeGenerator.Attributes;

[Disposable]
public partial class LogWriter : IDisposable {

    [Dispose(SetToNull = true)]
    private StreamWriter StreamWriter { get; set; }

    public LogWriter(string path) => StreamWriter = new StreamWriter(path);

    public void WriteLine(string text) {
        ThrowIfDisposed();
        StreamWriter.WriteLine($"{DateTime.Now}\t{text}");
    }
}

This generates the following partial class, which disposes the StreamWriter property and sets it to null.

partial class LogWriter
{
    /// <summary>
    /// Releases all resources used by the current instance.
    /// </summary>
    public void Dispose() {
        Dispose(disposing: true);
        global::System.GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Tracks whether the current instance has been disposed. This field must not be modified manually.
    /// </summary>
    private bool _isDisposed;

    /// <summary>
    /// Gets a value indicating whether the current instance has been disposed.
    /// </summary>
    protected virtual bool IsDisposed => _isDisposed;

    /// <summary>
    /// Throws an exception if the current instance has been disposed.
    /// </summary>
    protected void ThrowIfDisposed() {
        if (IsDisposed) {
            throw new global::System.ObjectDisposedException(nameof(LogWriter));
        }
    }

    /// <summary>
    /// Releases the unmanaged resources used by the current instance and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">"true" to release managed resources; otherwise, "false".</param>
    protected virtual void Dispose(bool disposing) {
        if (_isDisposed) {
            return;
        }
        _isDisposed = true;
        if (disposing) {
            StreamWriter?.Dispose();
        }
        StreamWriter = null;
    }

}

Async dispose

Use AsyncDispose for fields or properties that support DisposeAsync().

using System;
using System.IO;
using ReflectionIT.DisposeGenerator.Attributes;

[Disposable]
public partial class LogWriter : IAsyncDisposable {

    [AsyncDispose]
    private readonly StreamWriter _streamWriter;

    public LogWriter(string path) => _streamWriter = new StreamWriter(path);
}

This generates DisposeAsync() and DisposeAsyncCore(). If the same member also has [Dispose], the generator supports both sync and async cleanup patterns.

partial class LogWriter
{
    /// <summary>
    /// Asynchronously releases all resources used by the current instance.
    /// </summary>
    /// <returns>
    /// A task that represents the asynchronous dispose operation.
    /// </returns>
    public async global::System.Threading.Tasks.ValueTask DisposeAsync() {
        await DisposeAsyncCore().ConfigureAwait(false);
        global::System.GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Tracks whether the current instance has been disposed. This field must not be modified manually.
    /// </summary>
    private bool _isDisposed;

    /// <summary>
    /// Gets a value indicating whether the current instance has been disposed.
    /// </summary>
    protected virtual bool IsDisposed => _isDisposed;

    /// <summary>
    /// Throws an exception if the current instance has been disposed.
    /// </summary>
    protected void ThrowIfDisposed() {
        if (IsDisposed) {
            throw new global::System.ObjectDisposedException(nameof(LogWriter));
        }
    }

    /// <summary>
    /// Asynchronously releases the resources used by the current instance.
    /// </summary>
    /// <returns>
    /// A task that represents the asynchronous dispose operation.
    /// </returns>
    protected virtual async global::System.Threading.Tasks.ValueTask DisposeAsyncCore() {
        if (_isDisposed) {
            return;
        }
        _isDisposed = true;
        if (_streamWriter != null) {
            await _streamWriter.DisposeAsync().ConfigureAwait(false);
        }
    }

}

Implement the dispose pattern for a derived class

A class derived from a class that already implements IDisposable should not implement IDisposable again, because the base implementation of IDisposable.Dispose is inherited by derived classes.

Set OverrideDispose = true for the derived partial class. In that case, the public parameterless Dispose() method is not generated, and the protected Dispose(bool) method is generated as an override instead.

[Disposable(OverrideDispose = true)]
public partial class SecondLogWriter : LogWriter {

    [Dispose]
    private StreamWriter SecondStreamWriter { get; }

    public SecondLogWriter(string path) : base(path) => SecondStreamWriter = new StreamWriter(path + "2");

    public override void WriteLine(string text) {
        base.WriteLine(text);
        SecondStreamWriter.WriteLine($"{DateTime.Now}\t{text.ToUpper()}");
    }
}

This generates the following partial class, which disposes the SecondStreamWriter property.

partial class SecondLogWriter
{
    /// <summary>
    /// Tracks whether the current instance has been disposed. This field must not be modified manually.
    /// </summary>
    private bool _isDisposed;

    /// <summary>
    /// Gets a value indicating whether the current instance has been disposed.
    /// </summary>
    protected override bool IsDisposed => _isDisposed || base.IsDisposed;

    /// <summary>
    /// Throws an exception if the current instance has been disposed.
    /// </summary>
    protected void ThrowIfDisposed() {
        if (IsDisposed) {
            throw new global::System.ObjectDisposedException(nameof(SecondLogWriter));
        }
    }

    /// <summary>
    /// Releases the unmanaged resources used by the current instance and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">"true" to release managed resources; otherwise, "false".</param>
    protected override void Dispose(bool disposing) {
        if (_isDisposed) {
            return;
        }
        _isDisposed = true;
        if (disposing) {
            SecondStreamWriter?.Dispose();
        }
        base.Dispose(disposing);
    }

}

Unmanaged resources

Set HasUnmanagedResources = true to include unmanaged resource cleanup support. Then implement the partial method ReleaseUnmanagedResources(), which releases the unmanaged resource.

If you need to work with unmanaged resources, it is strongly recommended to wrap the unmanaged IntPtr handle in a SafeHandle.

[Disposable(HasUnmanagedResources = true)]
public partial class LogWriterWithAnExtraIntPtr : IDisposable {

    private readonly IntPtr _pointer;

    [Dispose]
    private StreamWriter StreamWriter { get; }

    public LogWriterWithAnExtraIntPtr(string path) {
        StreamWriter = new StreamWriter(path);
        _pointer = Marshal.AllocHGlobal(cb: 128);
    }

    public void WriteLine(string text) => StreamWriter.WriteLine($"{DateTime.Now}\t{text}");

    protected virtual partial void ReleaseUnmanagedResources() => Marshal.FreeHGlobal(_pointer);
}

This generates the following partial class with a finalizer and a partial method named ReleaseUnmanagedResources() that you must implement.

partial class LogWriterWithAnExtraIntPtr
{
    /// <summary>
    /// Releases all resources used by the current instance.
    /// </summary>
    public void Dispose() {
        Dispose(disposing: true);
        global::System.GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases unmanaged resources held by the current instance.
    /// </summary>
    ~LogWriterWithAnExtraIntPtr() {
        Dispose(disposing: false);
    }

    /// <summary>
    /// Releases unmanaged resources held by the current instance.
    /// </summary>
    protected virtual partial void ReleaseUnmanagedResources();

    /// <summary>
    /// Tracks whether the current instance has been disposed. This field must not be modified manually.
    /// </summary>
    private bool _isDisposed;

    /// <summary>
    /// Gets a value indicating whether the current instance has been disposed.
    /// </summary>
    protected virtual bool IsDisposed => _isDisposed;

    /// <summary>
    /// Throws an exception if the current instance has been disposed.
    /// </summary>
    protected void ThrowIfDisposed() {
        if (IsDisposed) {
            throw new global::System.ObjectDisposedException(nameof(LogWriterWithAnExtraIntPtr));
        }
    }

    /// <summary>
    /// Releases the unmanaged resources used by the current instance and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">"true" to release managed resources; otherwise, "false".</param>
    protected virtual void Dispose(bool disposing) {
        if (_isDisposed) {
            return;
        }
        _isDisposed = true;
        if (disposing) {
            StreamWriter?.Dispose();
        }
        ReleaseUnmanagedResources();
    }

}

Thread-safe disposal

Use IsThreadSafe = true to ensure thread-safe disposal via Interlocked.CompareExchange.

[Disposable(IsThreadSafe = true)]
public partial class LogWriter : IDisposable {

    [Dispose]
    private readonly StreamWriter _streamWriter;

    public LogWriter(string path) => _streamWriter = new StreamWriter(path);

    public void WriteLine(string text) => _streamWriter.WriteLine($"{DateTime.Now}\t{text}");
}

This generates the following partial class, which uses Interlocked.CompareExchange to ensure thread-safe disposal.

partial class LogWriter
{
    /// <summary>
    /// Releases all resources used by the current instance.
    /// </summary>
    public void Dispose() {
        Dispose(disposing: true);
        global::System.GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Detects redundant Dispose() calls in a thread-safe manner. _isDisposed == 0 means Dispose(bool) has not been called yet, and _isDisposed == 1 means Dispose(bool) has already been called. This field must not be modified manually.
    /// </summary>
    private int _isDisposed;

    /// <summary>
    /// Gets a value indicating whether the current instance has been disposed.
    /// </summary>
    protected virtual bool IsDisposed => _isDisposed != 0;

    /// <summary>
    /// Throws an exception if the current instance has been disposed.
    /// </summary>
    protected void ThrowIfDisposed() {
        if (IsDisposed) {
            throw new global::System.ObjectDisposedException(nameof(LogWriter));
        }
    }

    /// <summary>
    /// Releases the unmanaged resources used by the current instance and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">"true" to release managed resources; otherwise, "false".</param>
    protected virtual void Dispose(bool disposing) {
        if (global::System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) != 0) {
            return;
        }
        if (disposing) {
            _streamWriter?.Dispose();
        }
    }

}

Troubleshooting

Why do I get RITDG001?

The type marked with [Disposable] is not declared partial. Add the partial keyword to the class or struct declaration.

Why do I get compiler errors for Dispose() or DisposeAsync() on an annotated member?

The generator emits calls to Dispose() for [Dispose] members and DisposeAsync() for [AsyncDispose] members. Make sure the annotated member supports the corresponding API.

Why was no code generated?

Common reasons:

  • the type is not partial
  • no fields or properties were annotated with [Dispose] or [AsyncDispose]
  • the type only has invalid attribute usage that prevents successful compilation

License

This project is licensed under the MIT License. See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0-preview 58 4/29/2026
0.3.0-preview 55 4/28/2026
0.2.0-preview 58 4/20/2026
0.1.0-preview 52 4/20/2026

- Added a protected IsDisposed property used in the ThrowIfDisposed() method