Using reference-counting on IDisposable objects





5.00/5 (3 votes)
Reference Counting implementation using Extension Methods
In my current C# project, I have a need to share some IDisposable
objects between various classes of unknown lifetime. After searching the internet without success, I decided to come up with my own Reference Counting implementation using Extension Methods:
using System;
using System.Runtime.CompilerServices;
public static class IDisposableExtensions
{
/// <summary>
/// Values in a ConditionalWeakTable need to be a reference type,
/// so box the refcount int in a class.
/// </summary>
private class RefCount
{
public int refCount;
}
private static ConditionalWeakTable<IDisposable, RefCount> refCounts =
new ConditionalWeakTable<IDisposable, RefCount>();
/// <summary>
/// Extension method for IDisposable.
/// Increments the refCount for the given IDisposable object.
/// Note: newly instantiated objects don't automatically have a refCount of 1!
/// If you wish to use ref-counting, always call retain() whenever you want
/// to take ownership of an object.
/// </summary>
/// <remarks>This method is thread-safe.</remarks>
/// <param name="disposable">The disposable that should be retained.</param>
public static void Retain(this IDisposable disposable)
{
lock (refCounts)
{
RefCount refCount = refCounts.GetOrCreateValue(disposable);
refCount.refCount++;
}
}
/// <summary>
/// Extension method for IDisposable.
/// Decrements the refCount for the given disposable.
/// </summary>
/// <remarks>This method is thread-safe.</remarks>
/// <param name="disposable">The disposable to release.</param>
public static void Release(this IDisposable disposable)
{
lock (refCounts)
{
RefCount refCount = refCounts.GetOrCreateValue(disposable);
if (refCount.refCount > 0)
{
refCount.refCount--;
if (refCount.refCount == 0)
{
refCounts.Remove(disposable);
disposable.Dispose();
}
}
else
{
// Retain() was never called, so assume there is only
// one reference, which is now calling Release()
disposable.Dispose();
}
}
}
}
Note that, while this works fine, it is of course a very inefficient implementation of an already inefficient mechanism. Luckily, there are likely only a handful of objects requiring this. Use using{}
blocks whenever you can! Also, always consider whether it is worth the extra hassle of using explicit reference-counting, or whether you would be better off just letting the garbage collector handle the cleanup. If disposable objects are well written, they will have a destructor as well.
The nice thing is, however, that it works with any old IDisposable
implementing object, regardless of its origin (as long as it hasn't been casted to Object
, of course).
Warning: Newly created objects don't automatically get a refcount
of 1
, as you might expect. Rather, if you want to use reference counting on any object, you'll have to call Retain()
explicitly. Also, all the usual advice around reference-counted objects apply, i.e., don't create reference loops.
Update: Updated the code and text to incorporate some of Paulo Zemek's excellent feedback. His comment seems to have vanished, but thanks anyway!