Similar to this question, I am looking to use IDisposable for something other than what it was designed for.
The application for this isn't terribly relevant, but just for a good example: I have a Windows Forms application where I implement an Undo design pattern. Typically that is done by intercepting "Value Changed" sorts of events from UI elements. For a DataGridView
, CellEndEdit
and so forth. However there are cases in which I programmatically change data, and I want to do the things each Undo action tracks, without tracking them.
I have a way to do all that, but I manage my "Should I Undo" logic on a count:
private int _undoOverrides = 0;
public bool ShouldUndo { get { return _undoOverrides < 1; } }
public void DoNotUndo() { ++_undoOverrides; }
public void ResumeUndo() { --_undoOverrides; }
Now, this works well but you have to remember to call ResumeUndo()
at the end of such business logic that begins with DoNotUndo()
. And I thought:
Maybe I'm not too much of an idiot to screw that up, but what if I had to expose this interface in code I pass down? I would like if the compiler could take care of this for me, if possible.
I am considering using a class implementing IDisposable
for this. That way, a user of my code could use a using
block and never worry about housekeeping chores. This is what I have so far:
private static int _refCount = 0;
public static int ReferenceCount { get { return _refCount; } }
class HallPass : IDisposable
{
protected bool bActive;
public HallPass()
{
++Program._refCount;
bActive = true;
Console.WriteLine("Acquired hallpass!");
}
public void Dispose()
{
if (bActive)
--Program._refCount;
bActive = false;
Console.WriteLine("Hallpass expired!");
}
}
I included a boolean so that I'm sure I don't double-count back down on Dispose()
. So all you have to do to use a HallPass
is:
using (new HallPass())
{
// do things...
}
Is this a good idea? Why might this be a bad idea? Any gotchas I should know about?
Also, and I feel stupid for this, but I'm pretty sure reference count is not the right term for it. It's like a reference count, but there's no reference to manage or memory to free. Edit: It could be, it's just not right now.
It's like a mutex or a critical section in that you're trying to make an exception (another misnomer, because I don't mean the kind you throw
) to a rule during a section of code, but it's not either of those because they're meant to be mutually exclusive within a scope--this is designed to be done in a nested fashion if you wish. That's why it's a count, not a boolean.
The first concern of mine is Program._refCount
can be accessed from more than one threads and it is not being synchronized. But you can argue that your application is single threaded.
The next and bigger concern is you are not really using the disposable pattern in the way it is supposed to be used. I personally believe that using a pattern in the way it should be used is important, especially when you are not the only person who is working on the code.
ResumeUndo()
, now you need to keep in mind that you must call Dispose()
. Question is: will it be natural for your team members to realize that they need to call Dispose ()
when they want to use the HallPass
? (using
statement is nice, but it cannot be used in every scenario. If there is a HallPass
who lives longer than the scope of a single method, you cannot wrap it in a using
statement)Dispose()
on IDisposible
, it usually does not affect the correctness of your program - yes, your program will have performance problems, leaks etc., but functionally it usually is still correct. However for your HallPass
, if anyone forgets to call Dispose()
, I suppose there will be a functional bug. And the bug is hard to trace.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With