Structs that implement IDisposable

A few weeks ago some friends and I were at the pub discussing value types that implement IDisposable. Consider the IObservable interface.

interface IObservable<T>
{
    IDisposable Subscribe(IObserver<T> observer);
}

One *might* consider using a disposable value type to take some pressure off the GC.

struct SlimDisposable : IDisposable
{
  // ....    
}

Unfortunately this line of thinking is flawed. The value type will be boxed as an IDisposable, resulting in the heap allocation we were trying to avoid. Lets pretend our interface looked more like this;

interface IObservable<T>
{
    SlimDisposable Subscribe(IObserver<T> observer);
}

Or maybe;

interface IObservable<TValue,TDisposable> where TDisposable : IDisposable
{
    TDisposable Subscribe(IObserver<TValue> observer);
}

Now we could reference SlimDisposable without it being boxed! But is this a good thing? Unfortunately creating disposable value types is fraught with danger!

Eric Lippert has a great post on the subject here; To box or not to box, that is the question

To quote MSDN: “To help ensure that resources are always cleaned up appropriately, a Dispose method should be callable multiple times without throwing an exception.”

This means that a disposable object usually needs to mutate & track some state to determine if it has been disposed or not. But we are using a value type this state will be copied whenever we do an assignment. Consider the following;

public struct Disposable : IDisposable
{
    public bool IsDisposed;

    public void Dispose()
    {
        if(!IsDisposed)
        {
            Console.WriteLine("Disposing Resources");
            IsDisposed = true;
        }
    }
}

Now lets say you did something like this;

var d1 = new Disposable(); 
var d2 = d1; 
d1.Dispose(); 
Console.WriteLine("d1 == {0}, d2 == {1}", d1.IsDisposed, d2.IsDisposed);

OUTPUT

Disposing Resources

d1 == True, d2 == False

We now have two states & only 1 underlying resource!

Purely as an educational exercise, we decided to write a query that would find all the value types in the .NET framework that implement IDisposable. It turns out there are in fact quite a few

See for yourself;

from f in Directory.GetFiles (
       Path.Combine (
             Environment.GetFolderPath (System.Environment.SpecialFolder.ProgramFilesX86), 
             @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"),
       "*.dll")
where !f.ToLowerInvariant().Contains ("thunk")
where !f.ToLowerInvariant().Contains ("wrapper")
select Assembly.LoadFrom (f) into a
from t in a.GetExportedTypes()
where t.IsValueType && typeof(IDisposable).IsAssignableFrom (t)
select new { a.GetName().Name, t.FullName }

Capture

We’ve got some interesting types here;

System.Thread.AsyncFlowControl

System.Thread.CancellationTokenRegistration

System.Windows.Threading.DispatchProcessingDisabled

Both CancallationTokenRegistration & DispatchProcessingDisabled get around the mutable state problem; they are in fact immutable value types. They use the state of a parent object (a reference type) to determine if they have been disposed or not. This means they can be assigned/copied safely.

AsyncFlowControl is in fact a mutable value type! It does however use some state on the tread that is references to determine if the control flow is suppressed or not. However it does mean you can do weird things like this;

var afc1 = ExecutionContext.SuppressFlow();
var afc2 = afc1;
afc1.Dispose();
var afc3 = ExecutionContext.SuppressFlow();
afc2.Dispose();

This will result in the flow being restored, even though we never disposed of afc3. The BCL developers may have decided that in this case, the performance and pressure on the GC was so critical, that they’d commit this sin. Transferring ExecutionContext information from one thread to another is probably considered somewhat of a performance hotspot. I suspect it is also the case that very few developers have ever used this API let alone know what an ExecutionContext is!

Anyway, the real point of interest here is, “oh my god, what are all these enumerators!”

Next time – Structs that implement IEnumerator.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.