Simplifies the implementation of IDisposable.
See Milestones for release notes.
This is an add-in for Fody
It is expected that all developers using Fody become a Patron on OpenCollective. See Licensing/Patron FAQ for more information.
See also Fody usage.
Install the Janitor.Fody NuGet package and update the Fody NuGet package:
PM> Install-Package Fody
PM> Install-Package Janitor.Fody
The Install-Package Fody
is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.
Add <Janitor/>
to FodyWeavers.xml
<Weavers>
<Janitor/>
</Weavers>
- Looks for all classes with a
Dispose
method. - Finds all instance fields that are
IDisposable
and cleans them up. - Adds an
int disposeSignaled
field that isInterlocked.Exchange
ed insideDispose
. - Uses
disposeSignaled
to add an exit clause toDispose
. - Uses
disposeSignaled
to add a guard clause to all non-private instance methods. This will cause anObjectDisposedException
to be thrown if the class has been disposed. - Supports convention based overrides for custom disposing of managed and unmanaged resources.
- Adds a finalizer when clean-up of unmanaged resources is required
- Uses the
Dispose(isDisposing)
convention when clean-up of unmanaged resources is required
All instance fields will be cleaned up in the Dispose
method.
public class Sample : IDisposable
{
MemoryStream stream;
public Sample()
{
stream = new MemoryStream();
}
public void Method()
{
//Some code
}
public void Dispose()
{
//must be empty
}
}
public class Sample : IDisposable
{
MemoryStream stream;
int disposeSignaled;
bool disposed;
public Sample()
{
stream = new MemoryStream();
}
public void Method()
{
ThrowIfDisposed();
//Some code
}
void ThrowIfDisposed()
{
if (disposed)
{
throw new ObjectDisposedException("TemplateClass");
}
}
public void Dispose()
{
if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
{
return;
}
var temp = Interlocked.Exchange<IDisposable>(ref stream, null);
if (temp != null)
{
temp.Dispose();
}
disposed = true;
}
}
In some cases you may want to have custom code that cleans up your managed resources. If this is the case add a method void DisposeManaged()
public class Sample : IDisposable
{
MemoryStream stream;
public Sample()
{
stream = new MemoryStream();
}
public void Method()
{
//Some code
}
public void Dispose()
{
//must be empty
}
void DisposeManaged()
{
if (stream != null)
{
stream.Dispose();
stream = null;
}
}
}
public class Sample : IDisposable
{
MemoryStream stream;
int disposeSignaled;
bool disposed;
public Sample()
{
stream = new MemoryStream();
}
void DisposeManaged()
{
if (stream != null)
{
stream.Dispose();
stream = null;
}
}
public void Method()
{
ThrowIfDisposed();
//Some code
}
void ThrowIfDisposed()
{
if (disposed)
{
throw new ObjectDisposedException("TemplateClass");
}
}
public void Dispose()
{
if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
{
return;
}
DisposeManaged();
disposed = true;
}
}
In some cases you may want to have custom code that cleans up your unmanaged resources. If this is the case add a method void DisposeUnmanaged()
public class Sample : IDisposable
{
IntPtr handle;
public Sample()
{
handle = new IntPtr();
}
public void Method()
{
//Some code
}
public void Dispose()
{
//must be empty
}
void DisposeUnmanaged()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool CloseHandle(IntPtr hObject);
}
public class Sample : IDisposable
{
IntPtr handle;
int disposeSignaled;
bool disposed;
public Sample()
{
handle = new IntPtr();
}
void DisposeUnmanaged()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern Boolean CloseHandle(IntPtr handle);
public void Method()
{
ThrowIfDisposed();
//Some code
}
void ThrowIfDisposed()
{
if (disposed)
{
throw new ObjectDisposedException("TemplateClass");
}
}
public void Dispose()
{
if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
{
return;
}
DisposeUnmanaged();
GC.SuppressFinalize(this);
disposed = true;
}
~Sample()
{
Dispose();
}
}
Combining the above two scenarios will give you the following
public class Sample : IDisposable
{
MemoryStream stream;
IntPtr handle;
public Sample()
{
stream = new MemoryStream();
handle = new IntPtr();
}
void DisposeUnmanaged()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
void DisposeManaged()
{
if (stream != null)
{
stream.Dispose();
stream = null;
}
}
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool CloseHandle(IntPtr hObject);
public void Method()
{
//Some code
}
public void Dispose()
{
//must be empty
}
}
public class Sample : IDisposable
{
MemoryStream stream;
IntPtr handle;
int disposeSignaled;
public Sample()
{
stream = new MemoryStream();
handle = new IntPtr();
}
public void Method()
{
ThrowIfDisposed();
//Some code
}
void DisposeUnmanaged()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
void DisposeManaged()
{
if (stream != null)
{
stream.Dispose();
stream = null;
}
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern Boolean CloseHandle(IntPtr handle);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void ThrowIfDisposed()
{
if (disposeSignaled !=0)
{
throw new ObjectDisposedException("Sample");
}
}
public void Dispose(bool disposing)
{
if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
{
return;
}
if (disposing)
{
DisposeManaged();
}
DisposeUnmanaged();
}
~Sample()
{
Dispose(false);
}
}
Notice that the Dispose()
is empty in all of the above cases. This is because Janitor controls what goes in there. In fact if you put any code in there Janitor will throw an exception. If you want to control IDisposable
for specific types use [Janitor.SkipWeaving]
attribute applied to the type or [Janitor.SkipWeavingNamespace("namespaceToSkip")]
to the assembly. Then Janitor wont touch it.
So it is technically possible to flag a type, with an attribute or a custom interface, and inject the full implementation of IDisposable
. This would mean the empty Dispose
method would not be required. However, since Fody operates after a compile, it would mean you would not be able to use the type in question as if it was IDisposable
when in the same assembly. You would also not be able to use it as IDisposable
within the same solution since intellisense has no knowledge of the how Fody manipulates an assembly.
Not currently supported.
Spray Bottle designed by Julieta Felix from The Noun Project.