Skip to content

Instantly share code, notes, and snippets.

@njonsson
Created September 6, 2011 17:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save njonsson/1198180 to your computer and use it in GitHub Desktop.
Save njonsson/1198180 to your computer and use it in GitHub Desktop.
using System;
using System.Diagnostics;
/// <summary>
/// Serves as a wrapper around objects that require disposal but that do not
/// implement <see cref="System.IDisposable"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="Object"/></typeparam>
public abstract class DisposerBase<T> : IDisposable
{
/// <summary>
/// Indicates that <see cref="Object"/> is disposed.
/// </summary>
protected bool IsDisposed;
/// <summary>
/// Instantiates a new <see cref="DisposerByReflection{T}"/> object with the
/// specified <paramref name="object"/>.
/// </summary>
/// <param name="object">The value of <see cref="Object"/>.</param>
protected DisposerBase(T @object)
{
Object = @object;
}
~DisposerBase()
{
Debug.WriteLine(string.Format("{0} object was not disposed.",
typeof (IDisposable).FullName),
string.Format("You should call Dispose() on this {0} instead of letting it be finalized.",
GetType().FullName));
// ReSharper disable HeuristicUnreachableCode
Dispose(false);
// ReSharper restore HeuristicUnreachableCode
}
/// <summary>
/// The object to be disposed.
/// </summary>
public T Object { get; protected set; }
/// <summary>
/// Releases all resources used by the <see cref="DisposerBase{T}"/>.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="Object"/> is
/// already disposed.</exception>
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
/// <summary>
/// Releases the unmanaged resources used by the
/// <see cref="DisposerBase{T}"/> and optionally releases the managed
/// resources.
/// </summary>
/// <param name="disposing">If <c>true</c>, releases both managed and
/// unmanaged resources, otherwise releases only unmanaged
/// resources.</param>
/// <exception cref="ObjectDisposedException"><see cref="Object"/> is
/// already disposed.</exception>
protected void Dispose(bool disposing)
{
if (IsDisposed) throw new ObjectDisposedException(GetType().FullName);
IsDisposed = true;
DisposeImpl(disposing);
}
/// <summary>
/// Releases the unmanaged resources used by the
/// <see cref="DisposerBase{T}"/> and optionally releases the managed
/// resources.
/// </summary>
/// <param name="disposing">If <c>true</c>, releases both managed and
/// unmanaged resources, otherwise releases only unmanaged
/// resources.</param>
protected abstract void DisposeImpl(bool disposing);
}
using System;
/// <summary>
/// Serves as a wrapper around objects that require disposal but that do not
/// implement <see cref="System.IDisposable"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="object"/></typeparam>
public class DisposerByDelegation<T> : DisposerBase<T>
{
/// <summary>
/// Invoked when <see cref="DisposerBase{T}.Dispose"/> is called.
/// </summary>
protected readonly Action<T> DisposeAction;
/// <summary>
/// Instantiates a new <see cref="DisposerByDelegation{T}"/> object with the
/// specified <paramref name="object"/>.
/// </summary>
/// <param name="object">The value of <see cref="object"/>.</param>
/// <param name="disposeAction">The value of
/// <see cref="DisposeAction"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="object"/> is
/// <c>null</c>.</exception>
public DisposerByDelegation(T @object, Action<T> disposeAction)
: base(@object)
{
DisposeAction = disposeAction;
}
/// <summary>
/// Releases the unmanaged resources used by the
/// <see cref="DisposerByDelegation{T}"/> and optionally releases the
/// managed resources.
/// </summary>
/// <param name="disposing">If <c>true</c>, releases both managed and
/// unmanaged resources, otherwise releases only unmanaged
/// resources.</param>
protected override void DisposeImpl(bool disposing)
{
if (disposing)
{
if (DisposeAction != null) DisposeAction(Object);
}
}
}
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test
{
[TestClass]
public class DisposerByDelegationTest
{
protected class SomeObject
{
}
[TestMethod]
public void ShouldHaveTheExpectedObject()
{
SomeObject @object = null;
Assert.IsNull(new DisposerByDelegation<SomeObject>(@object,
null).Object);
@object = new SomeObject();
var disposer = new DisposerByDelegation<SomeObject>(@object, null);
Assert.AreSame(@object, disposer.Object);
}
[TestMethod]
public void ShouldDelegateTheDisposeMessageToDisposeAction()
{
var disposalCount = 0;
using (new DisposerByDelegation<SomeObject>(new SomeObject(),
o => disposalCount++))
{
}
Assert.AreEqual(1, disposalCount);
}
[TestMethod]
[ExpectedException(typeof (DivideByZeroException))]
public void ShouldBubbleDisposeActionExceptions()
{
using (new DisposerByDelegation<SomeObject>(new SomeObject(),
o => { throw new DivideByZeroException("Whoa, Nelly!"); }))
{
}
}
[TestMethod]
[ExpectedException(typeof (ObjectDisposedException))]
public void ShouldThrowObjectDisposedExceptionWhenSentDisposeTwice()
{
var @object = new SomeObject();
using (var disposer = new DisposerByDelegation<SomeObject>(@object,
null))
{
disposer.Dispose();
}
}
}
}
using System;
using System.Reflection;
/// <summary>
/// Serves as a wrapper around objects that require disposal but that do not
/// implement <see cref="System.IDisposable"/>.
/// </summary>
/// <typeparam name="T">The type of
/// <see cref="DisposerBase{T}.Object"/></typeparam>
public class DisposerByReflection<T> : DisposerBase<T>
{
private static MethodInfo GetPublicParameterlessInstanceMethod(T @object,
string methodName)
{
if (Equals(@object, null)) return null;
if (methodName == null) throw new ArgumentNullException("methodName");
const BindingFlags binding = BindingFlags.ExactBinding |
BindingFlags.Instance |
BindingFlags.Public;
var method = @object.GetType().GetMethod(methodName,
binding,
null,
CallingConventions.HasThis,
new Type[0],
null);
if (method == null)
{
throw new ArgumentException(string.Format("Object does not have a public, parameterless {0} instance method",
methodName));
}
return method;
}
/// <summary>
/// Instantiates a new <see cref="DisposerByReflection{T}"/> object with the
/// specified <paramref name="object"/> and a public, parameterless instance
/// <see cref="Method"/> named "Dispose".
/// </summary>
/// <param name="object">The value of
/// <see cref="DisposerBase{T}.Object"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="object"/> is
/// <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="object"/> does not
/// have a public, parameterless instance method named
/// "Dispose".</exception>
public DisposerByReflection(T @object) : this(@object, "Dispose")
{
}
/// <summary>
/// Instantiates a new <see cref="DisposerByReflection{T}"/> object with the
/// specified <paramref name="object"/> and the specified public,
/// parameterless instance <see cref="Method"/> named
/// <paramref name="methodName"/>.
/// </summary>
/// <param name="object">The value of
/// <see cref="DisposerBase{T}.Object"/>.</param>
/// <param name="methodName">The name of <see cref="Method"/>; must be a
/// public, parameterless instance method.</param>
/// <exception cref="ArgumentNullException"><paramref name="object"/> or
/// <paramref name="methodName"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="object"/> does not
/// have a public, parameterless instance method named
/// <paramref name="methodName"/>.</exception>
public DisposerByReflection(T @object, string methodName)
: this(@object,
GetPublicParameterlessInstanceMethod(@object, methodName))
{
}
/// <summary>
/// Instantiates a new <see cref="DisposerByReflection{T}"/> object with the
/// specified <paramref name="object"/> and <paramref name="method"/>.
/// </summary>
/// <param name="object">The value of
/// <see cref="DisposerBase{T}.Object"/>.</param>
/// <param name="method">The value of <see cref="Method"/>; must be a member
/// of <paramref name="object"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="object"/> or
/// <paramref name="method"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="method"/> is
/// static.</exception>
public DisposerByReflection(T @object, MethodInfo method) : base(@object)
{
if (Equals(@object, null))
{
throw new ArgumentNullException("object");
}
if (method == null)
{
throw new ArgumentNullException("method");
}
if (method.IsStatic)
{
throw new ArgumentException("Can't be a static method", "method");
}
if (method.GetParameters().Length > 0)
{
throw new ArgumentException("Can't be a method with parameters",
"method");
}
if (method.ContainsGenericParameters)
{
throw new ArgumentException("Can't be a method with unassigned generic type parameters",
"method");
}
Method = method;
}
/// <summary>
/// The <see cref="System.Reflection.MethodInfo"/> to which
/// <see cref="DisposerBase{T}.Dispose"/> will be delegated.
/// </summary>
public MethodInfo Method { get; protected set; }
/// <summary>
/// Releases the unmanaged resources used by the
/// <see cref="DisposerByReflection{T}"/> and optionally releases the
/// managed resources.
/// </summary>
/// <param name="disposing">If <c>true</c>, releases both managed and
/// unmanaged resources, otherwise releases only unmanaged
/// resources.</param>
protected override void DisposeImpl(bool disposing)
{
if (disposing)
{
try
{
Method.Invoke(Object, null);
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
}
}
}
using System;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test
{
[TestClass]
public class DisposerByReflectionTest
{
protected class WithCloseMethod
{
public void Close()
{
}
}
protected class WithDisposeMethod
{
public int DisposalCount;
public void Dispose()
{
DisposalCount++;
}
}
protected class WithDisposeMethodThatThrows
{
public void Dispose()
{
throw new DivideByZeroException("Whoa, Nelly!");
}
}
protected class WithGenericDisposeMethod
{
public void Dispose<T>()
{
}
}
protected class WithParameterizedDisposeMethod
{
public void Dispose(int foo)
{
}
}
protected class WithProtectedDisposeMethod
{
protected void Dispose()
{
}
}
protected class WithStaticDisposeMethod
{
public static void Dispose()
{
}
}
[TestMethod]
[ExpectedException(typeof (ArgumentNullException))]
public void ShouldThrowArgumentNullExceptionWhenConstructedWithNullObject()
{
new DisposerByReflection<WithDisposeMethod>(null);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowExpectedArgumentExceptionWhenLackingDisposeMethod()
{
var withoutDisposeMethod = new WithCloseMethod();
new DisposerByReflection<WithCloseMethod>(withoutDisposeMethod);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowExpectedArgumentExceptionWhenLackingInstanceDisposeMethod()
{
var withStaticDisposeMethod = new WithStaticDisposeMethod();
new DisposerByReflection<WithStaticDisposeMethod>(withStaticDisposeMethod);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowExpectedArgumentExceptionWhenLackingPublicDisposeMethod()
{
var withProtectedDisposeMethod = new WithProtectedDisposeMethod();
new DisposerByReflection<WithProtectedDisposeMethod>(withProtectedDisposeMethod);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowExpectedArgumentExceptionWhenLackingParameterlessDisposeMethod()
{
var withParameterizedDisposeMethod = new WithParameterizedDisposeMethod();
new DisposerByReflection<WithParameterizedDisposeMethod>(withParameterizedDisposeMethod);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowExpectedArgumentExceptionWhenLackingSpecifiedMethodByName()
{
var withoutCloseMethod = new WithDisposeMethod();
new DisposerByReflection<WithDisposeMethod>(withoutCloseMethod,
"Close");
}
[TestMethod]
[ExpectedException(typeof (ArgumentNullException))]
public void ShouldThrowArgumentNullExceptionWhenConstructedWithNullMethodName()
{
var withDisposeMethod = new WithDisposeMethod();
new DisposerByReflection<WithDisposeMethod>(withDisposeMethod,
(string) null);
}
[TestMethod]
[ExpectedException(typeof (ArgumentNullException))]
public void ShouldThrowArgumentNullExceptionWhenConstructedWithNullMethod()
{
var withDisposeMethod = new WithDisposeMethod();
new DisposerByReflection<WithDisposeMethod>(withDisposeMethod,
(MethodInfo) null);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowArgumentExceptionWhenConstructedWithAStaticMethod()
{
var withStaticDisposeMethod = new WithStaticDisposeMethod();
const BindingFlags binding = BindingFlags.Public |
BindingFlags.Static;
var staticMethod = withStaticDisposeMethod.GetType().GetMethod("Dispose",
binding);
new DisposerByReflection<WithStaticDisposeMethod>(withStaticDisposeMethod,
staticMethod);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowArgumentExceptionWhenConstructedWithAParameterizedMethod()
{
var withParameterizedDisposeMethod = new WithParameterizedDisposeMethod();
var parameterizedMethod = withParameterizedDisposeMethod.GetType().GetMethod("Dispose");
new DisposerByReflection<WithParameterizedDisposeMethod>(withParameterizedDisposeMethod,
parameterizedMethod);
}
[TestMethod]
[ExpectedException(typeof (ArgumentException))]
public void ShouldThrowArgumentExceptionWhenConstructedWithAnUnboundGenericMethod()
{
var withGenericDisposeMethod = new WithGenericDisposeMethod();
var unboundGenericMethod = withGenericDisposeMethod.GetType().GetMethod("Dispose");
new DisposerByReflection<WithGenericDisposeMethod>(withGenericDisposeMethod,
unboundGenericMethod);
}
[TestMethod]
public void ShouldNotThrowWhenConstructedWithABoundGenericMethod()
{
var withGenericDisposeMethod = new WithGenericDisposeMethod();
var unboundGenericMethod = withGenericDisposeMethod.GetType().GetMethod("Dispose");
var boundGenericMethod = unboundGenericMethod.MakeGenericMethod(typeof (DateTime));
new DisposerByReflection<WithGenericDisposeMethod>(withGenericDisposeMethod,
boundGenericMethod);
}
[TestMethod]
public void ShouldHaveTheExpectedObject()
{
var expectedObject = new WithDisposeMethod();
var disposer = new DisposerByReflection<WithDisposeMethod>(expectedObject);
Assert.AreSame(expectedObject, disposer.Object);
}
[TestMethod]
public void ShouldHaveTheExpectedDisposeMethodWhenNotSpecified()
{
var withDisposeMethod = new WithDisposeMethod();
const BindingFlags binding = BindingFlags.ExactBinding |
BindingFlags.Instance |
BindingFlags.Public;
var expectedMethod = withDisposeMethod.GetType().GetMethod("Dispose",
binding,
null,
CallingConventions.HasThis,
Type.EmptyTypes,
null);
var disposer = new DisposerByReflection<WithDisposeMethod>(withDisposeMethod);
Assert.AreSame(expectedMethod, disposer.Method);
}
[TestMethod]
public void ShouldHaveTheExpectedMethodWhenSpecifiedByName()
{
var withCloseMethod = new WithCloseMethod();
const BindingFlags binding = BindingFlags.ExactBinding |
BindingFlags.Instance |
BindingFlags.Public;
var expectedMethod = withCloseMethod.GetType().GetMethod("Close",
binding,
null,
CallingConventions.HasThis,
Type.EmptyTypes,
null);
var disposer = new DisposerByReflection<WithCloseMethod>(withCloseMethod,
"Close");
Assert.AreSame(expectedMethod, disposer.Method);
}
[TestMethod]
public void ShouldHaveTheExpectedMethodWhenSpecified()
{
var withCloseMethod = new WithCloseMethod();
const BindingFlags binding = BindingFlags.ExactBinding |
BindingFlags.Instance |
BindingFlags.Public;
var expectedMethod = withCloseMethod.GetType().GetMethod("Close",
binding,
null,
CallingConventions.HasThis,
Type.EmptyTypes,
null);
var disposer = new DisposerByReflection<WithCloseMethod>(withCloseMethod,
expectedMethod);
Assert.AreSame(expectedMethod, disposer.Method);
}
[TestMethod]
[ExpectedException(typeof (TargetException))]
public void ShouldThrowExpectedTargetExceptionUponDisposalWhenLackingTheSpecifiedMethod()
{
var withoutCloseMethod = new WithDisposeMethod();
var method = typeof (WithCloseMethod).GetMethod("Close");
using (new DisposerByReflection<WithDisposeMethod>(withoutCloseMethod,
method))
{
}
}
[TestMethod]
public void ShouldDelegateTheDisposeMessageToTheObject()
{
var withDisposeMethod = new WithDisposeMethod();
using (new DisposerByReflection<WithDisposeMethod>(withDisposeMethod))
{
}
Assert.AreEqual(1, withDisposeMethod.DisposalCount);
}
[TestMethod]
[ExpectedException(typeof (DivideByZeroException))]
public void ShouldThrowWhenObjectThrowsUponDisposal()
{
var withDisposeMethodThatThrows = new WithDisposeMethodThatThrows();
using (new DisposerByReflection<WithDisposeMethodThatThrows>(withDisposeMethodThatThrows))
{
}
}
[TestMethod]
[ExpectedException(typeof (ObjectDisposedException))]
public void ShouldThrowObjectDisposedExceptionWhenSentDisposeTwice()
{
var withDisposeMethod = new WithDisposeMethod();
using (var disposer = new DisposerByReflection<WithDisposeMethod>(withDisposeMethod))
{
disposer.Dispose();
}
}
}
}
@MrAntix
Copy link

MrAntix commented Jul 1, 2012

Great idea
How about using a delegate to call the dispose method, would also allow you to do multiple calls if required.

@njonsson
Copy link
Author

njonsson commented Jul 2, 2012

Thanks for the suggestion. I’ve added an implementation of event-oriented disposal.

@MrAntix
Copy link

MrAntix commented Jul 3, 2012

super, good work
but I was thinking of something like this

using(var disposer = new Disposer(new Thing(), o => { o.CloseThis(); o.CloseThat(); })){
    disposer.Object.DoStuff(...)        
}

// ctx 
Disposer<T>(T object, Action<T> onDispose)

@njonsson
Copy link
Author

njonsson commented Jul 3, 2012

Yes, I considered that, but at the time the event seemed more idiomatic. I agree that a parameterized inline delegate is highly convenient.

@njonsson
Copy link
Author

njonsson commented Jul 5, 2012

I just pushed an update that uses the inline delegate pattern you’ve advocated. I think it’s objectively better. There’s no conceivable need for multiple event handlers, and neither is an event sender nor an EventArgs useful, so an inline delegate is preferable. Thanks for the feedback.

@MrAntix
Copy link

MrAntix commented Jul 6, 2012

nice, thanks for this, it will be handy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment