Wednesday, December 25, 2013

"Using" Statements with Proxies

Summary

Every software developer who is doing more than simply churning out keywords and semicolons strives to create maintainable code.  In an effort to do so, we all have to take a step back and make decisions on how to handle certain complexities.  A common design that I find to be sloppy, is the try-finally statement.  I find that it does not grow well.

I think that examples do well to explain... Here, we're going to use a ReaderWriterLock.  This object in particular interests me, because it lacks a keyword that a normal lock enjoys (i.e. lock).

Normal way to use a ReaderWriterLock

var reader = new System.Threading.ReaderWriterLock();

try
{
  reader.AcquireWriterLock(TimeSpan.FromSeconds(1));

  // Do Work...
  // Blah blah blah
  // Foos and bars all over the place...
}
finally
{
  reader.ReleaseReaderLock();
}


While this works as expected, I find it hideous.  We have to instantiate the lock in a different scope than we use it and the ReleaseReaderLock() method has to be called in different scope than the AcquireWriterLock().  It just doesn't flow...

Nice to have...

Wouldn't it be nice if we were provided something like the lock keyword.  Then we could write something like the following:

var reader = new System.Threading.ReaderWriterLock();

lockReader(reader, TimeSpan.FromSeconds(1))
{
  // Do Work...
  // Blah blah blah
  // Foos and bars all over the place...
}


Way more concise!  Alas, no such beast exists.  So, why don't we create our own version!

Concise = Proxy + "Using" Statement

While we don't have a lockReader keyword available, we do have using.  So, I propose we create a wrapper class for the ReaderWriterLock that outputs a proxy whenever you acquire a reader or writer lock.  The proxy class will inherit IDisposable and the Dispose() method will release the lock. Simple enough?

Now, for some code:

First, the proxy class:

public class RWLockProxy : IDisposable
{
  private bool _readLock;
  private ReaderWriterLock _rwLock;

  private RWLockProxy()
  {
  }

  public static RWLockProxy AcquireReader(ReaderWriterLock rwLock, TimeSpan timeout)
  {
    rwLock.AcquireReaderLock(timeout);
    return new RWLockProxy { _rwLock = rwLock, _readLock = true };
  }

  public static RWLockProxy AcquireWriter(ReaderWriterLock rwLock, TimeSpan timeout)
  {
    rwLock.AcquireWriterLock(timeout);
    return new RWLockProxy { _rwLock = rwLock, _readLock = false };
  }

  public void Dispose()
  {
    if (_readLock)
      _rwLock.ReleaseReaderLock();
    else
      _rwLock.ReleaseWriterLock();
  }
}


Nothing too complicated here... Notice that I put all the ReaderWriterLock logic inside the proxy class.  I do this to keep everything together and to not spread logic out across multiple classes.  I find that it is easier to read and maintain.

Now for the wrapper class:

public class RWLock
{
  private readonly ReaderWriterLock _lock = new ReaderWriterLock();

  public RWLockProxy AcquireReader(TimeSpan timeout)
  {
    return RWLockProxy.AcquireReader(_lock, timeout);
  }

  public RWLockProxy AcquireWriter(TimeSpan timeout)
  {
    return RWLockProxy.AcquireWriter(_lock, timeout);
  }
}


Event simpler than the proxy!  Notice that we don't even provide the option to release the lock.  The proxy takes care of it.

And finally, here is the code using it:

var reader = new RWLock();

using(reader.AcquireReader(TimeSpan.FromSeconds(1)))
{
  // Do Work...
  // Blah blah blah
  // Foos and bars all over the place...
}


Just as concise as our non-existent lockReader!  Notice that we don't even care about the name of the proxy object.  We just let the using statement take it and dispose of it when we leave the scope.

Conclusion

So there you have it... in a nutshell, we have used a proxy class, a wrapper class and the using statement to create something very similar to the lock statement for the ReaderWriterLock and completely avoid a try-finally statement.  The code is easy to read, and should be easier to maintain.