Dirge

April 9, 2026 ยท View on GitHub

Test Version Download MIT License

Disposable Implementation Roslyn Generator Extension

Installation

You can install the EnumSerializer from NuGet.

Usage

Mark a class with the [AutoDispose] attribute and implement the IDisposable interface. The generator will automatically generate the implementation of the Dispose method for you.

using Dirge;

namespace Test;

[AutoDispose]
internal partial class TestClass
{
    private readonly Stream _stream = new MemoryStream();
}

The generated code will look like this:

namespace Test;

partial class TestClass : IDisposable
{
    private bool __generated_disposed = false;

    public void Dispose()
    {
        Dispose(true);
        global::System.GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (this.__generated_disposed) return;

        try
        {
            if (disposing)
            {
                this._stream?.Dispose();
            }
        }
        finally
        {
            this.__generated_disposed = true;
        }
    }
}

Note that this example is simplified for demonstration purposes.

To suppress the auto-generated Dispose call for a specific field, you can use the [DoNotDispose] attribute:

using Dirge;

namespace Test;

[AutoDispose]
internal partial class TestClass
{
    private readonly Stream _stream1 = new MemoryStream();

    [DoNotDispose]
    private readonly Stream _stream2 = new MemoryStream(); // This field will not be disposed by the generated Dispose method.
}

Ref-struct is also supported, but IDisposable will not be implemented regardless of the language version.

Conditional disposal

Conditional disposal, which allows you to specify conditions under which a field should be disposed, is also supported. You can use the [DoNotDisposeWhen] attribute with a boolean field and a value to compare against:

using Dirge;

namespace Test;

[AutoDispose]
internal partial class TestClass
{
    private readonly bool _leaveOpen;

    [DoNotDisposeWhen(nameof(_leaveOpen), true)]
    private readonly Stream _stream;

    internal TestClass(Stream stream, bool leaveOpen)
    {
        this._stream = stream;
        this._leaveOpen = leaveOpen;
    }
}

This will prevent the generator from disposing the _stream field when the _leaveOpen field is true:

namespace Test;

partial class TestClass : IDisposable
{
    private bool __generated_disposed = false;

    public void Dispose()
    {
        Dispose(true);
        global::System.GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (this.__generated_disposed) return;

        try
        {
            if (disposing)
            {
                if (!this._leaveOpen)
                {
                    this._stream?.Dispose();
                }
            }
        }
        finally
        {
            this.__generated_disposed = true;
        }
    }
}

Unmanaged resources

To safely release unmanaged resources, this generator also supports the implementation of a finalizer. You can specify a method to release unmanaged resources through the ReleaseUnmanagedResources option:

using Dirge;
using System.IO;
        
namespace Test;
        
[AutoDispose(ReleaseUnmanagedResources = nameof(ReleaseUnmanagedResources))]
internal sealed partial class TestClass
{
    private readonly Stream _stream;

    internal void ReleaseUnmanagedResources()
    {
        // Custom logic to release unmanaged resources
    }
}

This will generate a finalizer that calls the specified method to release unmanaged resources:

namespace Test;

sealed partial class TestClass : IDisposable
{
    private bool __generated_disposed = false;

    public void Dispose()
    {
        Dispose(true);
        global::System.GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (this.__generated_disposed) return;

        try
        {
            if (disposing)
            {
                this._stream?.Dispose();
            }

            ReleaseUnmanagedResources();
        }
        finally
        {
            this.__generated_disposed = true;
        }
    }

    ~TestClass()
    {
        Dispose(false);
    }
}

Constraints

To generate the Dispose method, the class (or struct) must meet the following constraints:

  • It must be a non-static class.
  • It must be a partial class or struct.
  • It must not be a readonly struct.

For conditional disposal, the field specified in the nameof expression must be a boolean field. Properties and methods are not supported for the current version.