Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added DisposableService and tests.

  • Loading branch information...
commit 50e9931f4f39601dc172ac23cbf78a51e58820e9 1 parent 75b6034
@bgrainger bgrainger authored
View
115 src/Logos.Utility/DisposableService.cs
@@ -0,0 +1,115 @@
+
+using System;
+using System.Diagnostics;
+
+namespace Logos.Utility
+{
+ /// <summary>
+ /// A thread-safe disposable service that supports property notifications.
+ /// </summary>
+ /// <remarks>See <a href="http://code.logos.com/blog/2008/03/threadsafe_disposable_objects.html">Thread-safe disposable objects</a>.</remarks>
+ public abstract class DisposableService : IDisposable
+ {
+ /// <summary>
+ /// Raised by the Dispose method immediately before disposing.
+ /// </summary>
+ /// <remarks>This event is raised only once.</remarks>
+ public event EventHandler Disposing
+ {
+ add
+ {
+ lock (m_disposingLock)
+ {
+ VerifyNotDisposed();
+ m_disposing += value;
+ }
+ }
+ remove
+ {
+ lock (m_disposingLock)
+ m_disposing -= value;
+ }
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ // only dispose once
+ EventHandler disposing;
+ lock (m_disposingLock)
+ {
+ if (m_isDisposing)
+ return;
+ m_isDisposing = true;
+ disposing = m_disposing;
+ m_disposing = null;
+ }
+
+ // this should never be null; it at least calls OnDisposing
+ disposing(this, EventArgs.Empty);
+
+ // mark as disposed
+ m_isDisposed = true;
+
+ // dispose
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DisposableService"/> class.
+ /// </summary>
+ protected DisposableService()
+ {
+ m_disposing = delegate { OnDisposing(); };
+ m_disposingLock = new object();
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ /// <remarks>This method is guaranteed to be called only once.</remarks>
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ /// <summary>
+ /// Verifies that the instance is not disposed.
+ /// </summary>
+ /// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
+ protected void VerifyNotDisposed()
+ {
+ if (m_isDisposed)
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ /// <summary>
+ /// Called during the call to Dispose, immediately before Dispose(bool) is called.
+ /// </summary>
+ /// <remarks>The object is not yet marked as disposed, so VerifyNotDisposed will not throw an exception.</remarks>
+ protected virtual void OnDisposing()
+ {
+ }
+
+#if DEBUG
+ /// <summary>
+ /// Releases unmanaged resources and performs other cleanup operations before the
+ /// <see cref="DisposableService"/> is reclaimed by garbage collection.
+ /// </summary>
+ /// <remarks>The finalizer is suppressed by Dispose, so this method should only be called if the client
+ /// fails to dispose the instance.</remarks>
+ ~DisposableService()
+ {
+ Debug.Fail("Not disposed: " + GetType().Name);
+ }
+#endif
+
+ readonly object m_disposingLock;
+ EventHandler m_disposing;
+ bool m_isDisposing;
+ volatile bool m_isDisposed;
+ }
+}
View
1  src/Logos.Utility/Logos.Utility.csproj
@@ -45,6 +45,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DictionaryUtility.cs" />
+ <Compile Include="DisposableService.cs" />
<Compile Include="HashCodeUtility.cs" />
<Compile Include="ObjectImpl.cs" />
<Compile Include="ObjectUtility.cs" />
View
87 tests/Logos.Utility.Tests/DisposableServiceTests.cs
@@ -0,0 +1,87 @@
+
+using System;
+using NUnit.Framework;
+
+namespace Logos.Utility.Tests
+{
+ [TestFixture]
+ public class DisposableServiceTests
+ {
+ [Test]
+ public void Dispose()
+ {
+ using (TestService service = new TestService())
+ {
+ Assert.That(service.OnDisposingCalled, Is.EqualTo(0));
+ Assert.That(service.DisposeCalled, Is.EqualTo(0));
+ Assert.That(service.Method(), Is.EqualTo(1));
+
+ service.Dispose();
+
+ Assert.That(service.OnDisposingCalled, Is.EqualTo(1));
+ Assert.That(service.DisposeCalled, Is.EqualTo(1));
+ Assert.Throws<ObjectDisposedException>(() => service.Method());
+
+ service.Dispose();
+
+ Assert.That(service.OnDisposingCalled, Is.EqualTo(1));
+ Assert.That(service.DisposeCalled, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void DisposingEvent()
+ {
+ int called = 0;
+
+ using (TestService service = new TestService())
+ {
+ service.Disposing += (s, e) => called++;
+ Assert.That(called, Is.EqualTo(0));
+ Assert.That(service.Method(), Is.EqualTo(1));
+
+ service.Dispose();
+ Assert.That(called, Is.EqualTo(1));
+ Assert.Throws<ObjectDisposedException>(() => service.Method());
+
+ service.Dispose();
+ Assert.That(called, Is.EqualTo(1));
+ }
+ }
+
+ private class TestService : DisposableService
+ {
+ public int OnDisposingCalled { get; private set; }
+
+ public int DisposeCalled { get; private set; }
+
+ public int Method()
+ {
+ VerifyNotDisposed();
+
+ return 1;
+ }
+
+ protected override void OnDisposing()
+ {
+ // shouldn't throw
+ VerifyNotDisposed();
+
+ OnDisposingCalled++;
+ base.OnDisposing();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ DisposeCalled++;
+ }
+ finally
+ {
+ base.Dispose(disposing);
+ }
+ }
+ }
+ }
+}
View
1  tests/Logos.Utility.Tests/Logos.Utility.Tests.csproj
@@ -50,6 +50,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DictionaryUtilityTests.cs" />
+ <Compile Include="DisposableServiceTests.cs" />
<Compile Include="EquatableClass.cs" />
<Compile Include="EquatableClassTests.cs" />
<Compile Include="EquatableStruct.cs" />
Please sign in to comment.
Something went wrong with that request. Please try again.