Monday, February 12, 2007 2:59 PM
I've been spending a lot of time recently thinking about how you could enforce calling restrictions on certain methods in your objects. Say, for example, you have a particular method on a particular class that essentially is in place only as a kluge; something that should only be called in very certain circumstances and only by a certain caller.
Let me give you a example. Recently I was asked to make a certain method asynchronous, a method which previously had been invoked completely synchronously. I decided to just follow the standard .NET Asynchronous Programming Model and essentially wrap the method in a modified BackgroundWorker (the solution actually ended up being a bit more involved than that but that was essentially the gist of it).
The BackgroundWorker, like most of the APM, works on delegates. Essentially, you find the method that does most of your work and designate that as the handler for your BackgroundWorker.DoWork event. Then you find your clean up method which should be called at the end (this is basically your callback) and designate that as the handler for your BackgroundWorker.RunWorkerCompleted event. Once you get your head around it it's actually a pretty slick little system.
Let's say that your original methods are DoIntensiveOperation() and CleanUpIntensiveOperation(), like below:
private void DoIntensiveOperation()
{
// Perform a lot of intensive work
}
private void CleanUpIntensiveOperation()
{
// Clean up our intensive operation
}
and it's called in code synchronously like this:
Console.WriteLine("Starting our intensive operation...");
DoIntensiveOperation();
CleanUpIntensiveOperation();
Console.WriteLine("Finished with our intensive operation!");
If you want to make the method asynchronous, you simply have to register it as appropriate with the BackgroundWorker and call the BackgroundWorker in its place. (OK, I oversimplified it a little bit, you also have to alter your original method signatures to match the expected signatures the handlers in BackgroundWorker expect).
BackgroundWorker myBackgroundWorker = new BackgroundWorker();
myBackgroundWorker.DoWork +=
new DoWorkEventHandler(DoIntensiveOperation);
myBackgroundWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(CleanUpIntensiveOperation);
Console.WriteLine("Starting our intensive operation...");
myBackgroundWorker.RunWorkerAsync();
Console.WriteLine("Finished with our intensive operation!");
Simple enough, right? Well, almost.
Think about this: Now you have new versions of those methods out there which are really only invoked by the background worker. Granted in the example above, their private but there's no reason why they couldn't be public. And besides, just because they're private doesn't necessarily make them safe. In my case, I was working in a shared class file. There was nothing to keep the next developer from coming along 5 minutes later and deciding to just randomly start calling my precious methods directly; scary signatures not withstanding.
What we need is a way to verify that those new methods we created are only being called by who we intended, in our case, the BackgroundWorker. Luckily, the signature provides just such a way.
You'll notice that the signature of our methods bear a strong resemblance to signature of the EventHandler delegate, as well they should since they're in essence handlers in they're own right. One of the interesting things about the EventHandler delegate is that they're first parameter is and object called 'sender'. What is this 'sender'? Well, if you've ever needed to inspect it while handling an event, you know that the runtime implicitly places a references to the calling object in this sender parameter meaning that you can always reference the calling object (or at least its public members) when you're handling the event. Luckily, that works out to be just exactly what we need to verify who's actually calling our method. We can check this parameter as soon as we're invoked and decide if who called us is someone that we want to be allowed to call us.
private void DoIntensiveOperation(object sender, DoWorkEventArgs e)
{
Debug.Assert(sender is BackgroundWorker,
"DoIntensiveOperation may only be called by an instance of BackgroundWorker");
// Perform a lot of intensive work
}
private void CleanUpIntensiveOperation(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Assert(sender is BackgroundWorker,
"CleanUpIntensiveOperation may only be called by an instance of BackgroundWorker");
// Clean up our intensive operation
}
Works great, right? Well, almost. One of the gotchas with this is that it only makes sense for methods which either can be invoked publicly by an outside object, or asynchronously by use of a delegate. It really just doesn't make to much sense for your standard synchronous private method. The other gotcha is that this methodology relies heavily on the honor system, meaning, that it's very easy to spoof:
Console.WriteLine("Starting our intensive operation...");
DoIntensiveOperation(new BackgroundWorker(), DoWorkEventArgs.Empty);
CleanUpIntensiveOperation(new BackgroundWorker(), RunWorkerCompletedEventArgs.Empty);
Console.WriteLine("Finished with our intensive operation!");
So its a possible solution, but definitely not an end all. It's definitely not a solution I would recommend for a commercially developed API or any API with a high security standard but it may work OK to enforce proper use inside of your own little development team. Anyone have any thoughts?