I am implementing a class library and looking for a way to restrict the number of instances of a given class the library will allocate to a pre-set number. The restriction has to be machine wide - a simple static
counter isn't enough as this will only count the instances in the calling process. I want to keep things as simple as possible (no memory mapped files etc) and as secure as possible (no storing counters in temp files or the registry) so decided to try using a global shared semaphore as the "counter"
public class MyClass : IDisposable
{
// Limit to 10 instances
Semaphore m_sem = new Semaphore(10, 10, "SharedName");
public MyClass()
{
if(!m_sem.WaitOne(0))
{
throw new Exception("No instances free");
}
}
void IDisposable.Dispose()
{
m_sem.Release();
}
}
That seems to work OK. However if Dispose()
doesnt get called, the semaphore never gets released - essentially "leaking" the instance. Now IMHO IDisposable
is one of the the worst parts of .NET - I see far more code missing using( ... ) {}
than with it. Even worse when you use a IDisposable
as a data member and watch as "IDisposable cancer" spreads though every class in your app.
So I decide to implement the full IDisposable
(anti-)pattern for people who forget the using()'s.
public class MyClass : IDisposable
{
// Limit to 10 instances
Semaphore m_sem = new Semaphore(10, 10, "SharedName");
public MyClass()
{
if(!m_sem.WaitOne(0))
{
throw new Exception("No instances left");
}
}
~MyClass()
{
Dispose(false);
}
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if(disposing)
{
m_sem.Release();
}
else
{
// To release or not release?
// m_sem.Release();
}
}
}
Things are simple when being called correctly with using
, I release the semapore. But when being called as a last resort by the finalize, as far as I understand it I should not access managed resources as the destruction order isn't fixed - m_sem may have been destroyed.
So, how can I release the semaphore in the case where the user forgets using
? (RTFM may be a valid answer, but one I hope to avoid). As it stands, the "leaked" instance is counted right up until the final process that is using my assembly terminates (at which point I assume the global semaphore is freed)
Or indeed is there a better way of doing this?