Patterns in .NET: Citing the Command Pattern
Of late, I am getting more and more interested in Design Patterns. As Allan Shalloway, in his most excellent book, Design Patterns Explained explains, design patterns are a great way to understand Object Oriented Programming better. Though design patterns are not restricted to the seminal work from the GOF, they do provide a solid basis on how patterns, in general are conceived. I understand that there are umpteen books and resources on patterns, especially those in C# and VB.NET. What I am trying to do here try to cite certain patterns that exist with the BCL, albeit not clearly visible, and also reflect certain problem-solution pairs that we are all way too familiar with, but don’t classify them as patterns. Let me start with an interesting problem we face quite often:
You need to execute a method asynchronously. Now, the Thread and the ThreadPool class allow you to do that, but there is a catch. They accept only specific types of delegates - ThreadStart and WaitCallback respectively. Therefore, you cannot asynchronously call methods with arbitrary signatures directly. So, what's the solution? The answer is Command Pattern. Essentially, in this pattern, you encapsulate the command/operation to be executed as an object, with the method parameters represented by the state of command object. So, to apply the command pattern to our problem, we would need to create a command class that can execute any method, irrespective of the signature. The best way to handle this is by using Delegate.DynamicInvoke method. Therefore, the state of our Command object would contain a delegate instance and an object array, which is passed to the DynamicInvoke method and also, optionally, a result object. Here's how:
public class Command
{
private readonly Delegate _target;
private readonly object[] _args;
private object _result;
public Command(Delegate d, object[] args)
{
_target = d;
_args = args;
}
public void InvokeCommand()
{
_result = _target.DynamicInvoke(_args);
}
public object Result
{
get { return _result; }
}
}
Now, to execute this command/operation asynchronously, we can use the Thread/ThreadPool class. Given below is a helper that wraps the ThreadPool class. All you need to do is call the static method Invoke passing it an instance the command object. Once again, this command object represents the job to be executed. Note that the Command object is passed as a state object (second parameter) to the QueueUserWorkItem method.
//Just a wrapper to the thread pool class
public class ThreadPoolHelper
{
private static WaitCallback dynamicInvokeShim = new WaitCallback(CommandInvoker);
private static void CommandInvoker(object o)
{
Command command = (Command) o;
command.InvokeCommand();
}
public static bool Invoke(Command command)
{
return ThreadPool.QueueUserWorkItem(dynamicInvokeShim, command);
}
}
Now let's see how the Command is used (the 'Receiver'). Shown below is a totally arbitrary class using the command:
public class MyClass
{
private delegate int AddDelegate(int a, int b);
public void TestCommandPattern()
{
Delegate dlg = new AddDelegate(Add);
//Command to invoke the add operation
Command command = new Command(dlg, new object[] {5, 5});
ThreadPoolHelper.Invoke(command);
//Command to invoke the do nothing operation
command = new Command(new ThreadStart(JustDoIt), null);
ThreadPoolHelper.Invoke(command);
}
//Sample method
private int Add(int a, int b)
{
return a + b;
}
//Sample method
private void JustDoIt()
{
Console.WriteLine("Nothing!");
}
}
Now, many of you would be ready to pounce on me with the suggestion that BeginInvoke/EndInvoke combination on the delegate offers a similar solution, perhaps more cleanly & efficiently. Well yes, I do agree. I gave the above example as a good illustration of the command pattern and moreover, this method is still apt if you employ the Thread class (Note the BeginInvoke involves a threadpool thread)
In my subsuquent posts, I shall try to cite more such application of patterns in .NET. If there are better examples, or if my citations are flawed in any way, please feel free to drop in your comments. Thanks!