Growing IDisposable Guided By Tests

Test 2: When Dispose() is called all unmanaged resources are released
[Fact]
public void ReleasesUnmanagedResourcesFromDispose()
{
    var spy = new ResourceSpy();
    spy.Dispose();
    spy.UnmanagedResourcesHaveBeenReleased.Should().BeTrue();
    GC.SuppressFinalize(spy);
}

To make it compile we add:

public class ResourceOwner
{
    ...
    public void Dispose(){}
}

The test fails. To make it pass we release the unmanaged resources:

public class ResourceOwner
{
    ...
    public void Dispose()
    {
        Marshal.FreeCoTaskMem(AnUnmanagedResource);
    }
}

This introduces some duplication, but we will hold off refactoring until we have a clearer picture of the shape of the code.
The test passes but the test suite crashes again. This time we do need to modify the ResourceOwner so that the finalizer will not be called after Dispose(), which leads to the next test…

 

Test 3: The Dispose() method must suppress finalization

Once Dispose() has been called by the client and the unmanaged resources have been released, we need to inform the GC that finalization is no longer required. The reference to the ResourceOwner will then be removed from the Finalization Queue (and consequenntly the GC will no longer initiate finalization). The ResourceOwner will be now be Garbage Collected at the first Generational GC after it goes out of scope as it will no longer have any other root references, thus avoiding an unnecessary generational promotion and further delays to reclaiming the unused heap space.
Unfortunately there is no way to test this that I can think of as GC is a static class, death for testing. We must simply implement it:

public class ResourceOwner
{
    ...
    public void Dispose()
    {
        Marshal.FreeCoTaskMem(AnUnmanagedResource);
        GC.SuppressFinalize(this);
    }
}

 

Test 4: The ResourceOwner implements IDisposable

Now that we have a Dispose() method, the simplest next step is to implement IDisposable:

[Fact]
public void ImplementsIDisposable()
{
    var resourceOwner = new ResourceOwner() as IDisposable;
    resourceOwner.Should().NotBeNull();
}

The test fails. To make it pass we implement IDisposable:

public class ResourceOwner : IDisposable
    {
        ...
    }

The test passes.

2 thoughts on “Growing IDisposable Guided By Tests

  1. Regarding test 3, you could check your call to GC.SuppressFinalize(this) by introducing a non-public instance method SuppressFinalizeOnThis() which delegates to GC. This then acts like a collaborator that, in some languages, you might pull out into a mixin/trait.

    I don’t think I’d suggest it until you had reason to believe that you weren’t calling GC.SuppressFinalize() correctly.

    1. Hi J.B. Thanks for the reply.

      I thought about doing something like that but it just seems to shift the problem (if I am understanding you correctly). While this would allow me to test that SuppressFinalizeOnThis() was called, the thing I really need to test is that GC.SuppressFinalize(this) was called. I would still need to confirm that this call was being made inside SuppressFinalizeOnThis(). I don’t know how to do that except by visual inspection, which gets me back to the original problem.

Comments are closed.