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 }
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.



