Introduction
In this installment of the series, I'll show you how you can use the LinFu.Delegates
library to handle any event fired from any object, regardless of its event signature. I will also show you how to use LinFu
's Closure
s, which can point to any method or delegate and allow you to bind any arbitrary value to any of the target method's (or delegate's) parameters at runtime. Note: If you're wondering what the entire LinFu
Framework is about, click here.
Background
With the advent of delegates in .NET 1.0, Microsoft came up with an effective way to separate event publishers from event subscribers while still being able to adhere to the strongly-typed features of its more prominent statically-typed programming languages, such as VB.NET and C#. However, because these languages are indeed statically typed, that separation between publishers and subscribers is, at best, incomplete. This is because it requires the subscriber to know about the signature of the event it will be handling in order to handle that event once it is fired. For example, in order to handle a Windows Forms Button.Click
event, I would have to manually write a handler that would look something like this:
private void button1_Click(object sender, System.EventArgs e)
{
Console.WriteLine("Button Clicked!");
}
The problem here is that each handler requires a developer to write a method to match the signature for each event that must be handled in any given application.
So, What's the Problem?
This would be perfectly fine if my applications were to handle only a handful of events. In practice, however, there are myriad events that can be fired from several components in any given application. Manually having to manage each one of these event handlers can be tedious, at best. The main problem here is that the more events a developer has to handle, the more complicated the application becomes. For me, there had to be a better way to go about handling events and delegates. Thus, the LinFu.Delegates
library was born.
Features and Usage
The LinFu.Delegates
library has a few useful features that make managing delegates and events easier than having to manage them through traditional methods. These features are:
Universal Event Handling
Believe it or not, using LinFu
to bind to any event regardless of its signature requires only a single line of code. For example, let's say that I already have a CustomDelegate
-typed handler defined and I wanted to attach it to a Button.Click
event:
CustomDelegate handler = delegate
{
Console.WriteLine("Button Clicked!");
return null;
};
Button myButton = new Button();
EventBinder.BindToEvent("Click", myButton, handler);
As you can see from the example above, the EventBinder
class
knows absolutely nothing about the signature of the Button.Click
event at compile time. If that's the case, then how is it able to route the event back to a CustomDelegate
instance?
All in the Reflection
The answer is that the EventBinder
class is smart enough to generate its own Button.Click
event handler at runtime and attach itself to the target event. Behind the scenes, it dynamically generates a method implementation that matches the event signature. It routes all its calls back to the CustomDelegate
instance, allowing you to handle any event just by using a CustomDelegate
instance.
Event Arguments
A single method call is all you need to attach to any event defined on any class instance, but what if you needed to extract the EventArgs
argument when handling that same Click
event? Here's how you'd do it:
CustomDelegate handler = delegate(object[] args)
{
object source = args[0];
EventArgs e = (EventArgs)args[1];
Console.WriteLine("Button Clicked!");
return null;
};
Button myButton = new Button();
EventBinder.BindToEvent("Click", myButton, handler);
Once an event is fired and rerouted to a CustomDelegate
using the EventBinder
, each one of the event arguments is passed down to the CustomDelegate
handler. In this case, the standard event signature will always have a source
and an EventArgs
parameter, so this handler won't have any problem extracting any arguments from the args
parameter. This example will handle approximately 80% of cases where the event signature follows the standard event signature. For the remaining 20% of cases where there are non-standard event signatures, however, the onus is on you to figure out what to do with those arguments. That is an exercise I leave to the capable hands of the reader. With that, let's move on to the next section.
Closures
If you're familiar with using anonymous delegates in C# versions 2.0 and above, then you should have an idea of what closures are. This is because you are already using them and you probably never realized it. As most .NET developers know, a delegate is a pointer to a method and an anonymous delegate (in C# 2.0) is a delegate without a name... but what exactly is a closure?
The Quick and Dirty™ Explanation
Aside from its highly technical origins in functional programming languages such as Lisp, you can think of closures as delegates that contain some form of state (other than whatever method they are pointing to). In C#, for example, anonymous delegates can refer to variables that are declared outside the anonymous method block:
string format = "Hello, {0}!";
Action<string> speak = delegate(string name)
{ Console.WriteLine(format, name); };
speak("World!");
In this case, since the anonymous method block that the speak
delegate is pointing to is actually using the outer format
variable, one could say that anonymous delegates are effectively a form of implicitly-declared closures. This is because they capture the state of the variables that they use, even if those same variables are declared from outside the scope of the anonymous method. Yes, I too had to read that sentence twice.
Currying and Lambda Arguments
In contrast, for some functional programming languages (such as Lisp, Haskell or even F#), the state that a closure holds is not the value of the variables outside the (hypothetical) anonymous delegate block "per se." Rather, the state being held is the list of parameter values that are going to be passed to the target method. For example, suppose that I had an Add
method that takes two numbers as parameters and returns the sum as the result:
public int Add(int first, int second)
{
return first + second;
}
In a functional programming language, I can actually create custom implementations of the Add
method (or, for simplicity's sake, let's call it a delegate) that have the first
or second
parameter (or even both parameters) hard-wired to a specific value. For example, let's say I wanted to customize the Add
method so that the result would add one (the hard-wired value of the second
parameter) to the value of the first
parameter. Logically speaking, this is how the newly-derived method would look in C#:
public int Add(int first)
{
int second = 1;
return first + second;
}
The Catch
Other than by using LinFu
, there isn't any language feature native to VB.NET or C# that lets you dynamically derive new method definitions in this fashion. Fortunately, LinFu
provides a Closure
class that implements all of the functionality that I just mentioned above. Some of its features are:
Delegates and Argument Binding
The Closure
class can take any existing delegate or method and arbitrarily bind any one of its parameters to a specific value, such as in this example:
public delegate int MathOperation(int a, int b);
public static class Program
{
public static void Main()
{
MathOperation add = delegate(int a, int b) { return a + b; };
Closure addOne = new Closure(add, Args.Lambda, 1);
int result = (int)addOne.Invoke(2);
}
}
Most of the code in the above example is self-explanatory, except for the Args.Lambda
value. When the Args.Lambda
value is passed to the closure as a parameter value, it simply tells the closure to replace that Args.Lambda
value with an actual value once the Invoke(
)
method is called. In this example, the Invoke()
method was called with the value of 2
passed as the parameter. Since Args.Lambda
was defined in the first argument, the resulting method call is equivalent to calling the original Add
delegate using the following parameters:
int result = add(2, 1);
The above example is useful for handling simple scenarios, but what about situations where one might need to use a Closure
as a nested argument?
Nesting Lazy Method Calls
As you probably guessed by now, Closure
s are nothing more than deferred method calls that are evaluated once you call the Closure.Invoke
(
)
method. As such, they can be used as deferred arguments within another Closure
instance and they can be evaluated once the outer Closure
's Invoke(
)
method is called. Here's a simple example:
public delegate int MathOperation(int a, int b);
public static class Program
{
public static void Main()
{
MathOperation add = delegate(int a, int b) { return a + b; };
Closure inner = new Closure(add, 1, 1);
Closure outer = new Closure(add, inner, 1);
int result = (int)outer.Invoke();
}
}
Once the outer
closure is invoked in this example, it first evaluates the inner
closure, which returns the value of 2
. Next, it takes the supplied parameter value of 2
(evaluated from the inner
closure)
and the value of 1
(supplied as part of the outer
closure's constructor) and passes them to the actual add
delegate. Once the add
delegate is called, it simply performs the mathematical operation and returns the result.
Instance Method Binding
The Closure
class also allows you to bind to any existing instance method using the following constructor:
public Closure(object instance, MethodInfo targetMethod,
params object[] suppliedArguments);
Binding a closure
to an instance method is as simple as this:
MathClass math = new MathClass();
MethodInfo method = typeof (MathClass).GetMethod("Add");
Closure closure = new Closure(math, method, 1, 1);
int result = (int)closure.Invoke();
Using the Closure with DynamicObject
If you need the same functionality without having to use reflection, then using the Closure
class with LinFu.DynamicObject
might be just what you need:
MathClass math = new MathClass();
DynamicObject dynamic = new DynamicObject(math);
Closure closure = new Closure(dynamic.Methods["Add"], 1, 1);
int result = (int)closure.Invoke();
In this example, the DynamicObject
will perform the late bound call using the arguments supplied in the Closure
constructor. The DynamicObject
will automatically determine which method to call, thus saving you the trouble of having to deal with Reflection.
Static Method Binding
The Closure
class also supports binding itself to static methods using the following constructor:
public Closure(MethodInfo staticMethod,
params object[] suppliedArguments);
The only difference between this constructor and the one used for instance methods is that it doesn't require an object instance in order to execute. Other than that, the other constructor parameters are self-explanatory.
Closures and Dynamically Generated Methods
There might be times, however, when you need to have the closure point to a dynamically generated method instead of defining a custom delegate type for each method that you want to use. In such cases, the Closure
class has a special constructor that allows you to bind it to a dynamically generated method:
public Closure(CustomDelegate body, Type returnType,
Type[] parameterTypes, params object[] suppliedArguments);
Using this constructor, I can create a strongly-typed Closure
instance using the following code:
CustomDelegate addMethodBody = delegate(object[] args)
{
int a = (int)args[0];
int b = (int)args[1];
return a + b;
};
Type returnType = typeof (int);
Type[] parameterTypes = new Type[] {typeof(int), typeof(int)};
Closure closure =
new Closure(addMethodBody, returnType, parameterTypes, 1, 1);
int result = (int)closure.Invoke();
Custom Methods
Once the constructor is called, closure
dynamically generates a method with the given returnType
and parameterTypes
as its signature. That same method will use the CustomDelegate
instance as its method body. Any arguments provided in the suppliedArguments
parameter, in turn, will be evaluated in the same manner as the examples that I have given above.
Adapting Closures to Any Delegate Signature
The Closure
class is also capable of adapting itself to any delegate type, using the AdaptTo
(
)
method:
public delegate int MathOperation(int a, int b);
CustomDelegate addMethodBody = delegate(object[] args)
{
int a = (int)args[0];
int b = (int)args[1];
return a + b;
};
Type returnType = typeof (int);
Type[] parameterTypes = new Type[] {typeof(int), typeof(int)};
Closure closure = new Closure(addMethodBody, returnType, parameterTypes,
Args.Lambda, Args.Lambda);
MathOperation add = closure.AdaptTo<MathOperation>();
int result = add(3, 3);
The only difference between this example and the previous example is that I'm actually using an instance of a MathOperation
delegate to invoke addMethodBody
. Behind the scenes, however, the call to the MathOperation
delegate is actually routed back to the Closure
instance which, in turn, routes the method call back to CustomDelegate
. In a nutshell, you can think of it as duck typing for delegates.
Where This Might Be Useful
This feature can be useful if you want to dynamically generate your own custom handlers at runtime. When combined with LinFu.DynamicObject
, the possibilities are truly endless.
Points of Interest
Practical Uses for Closures
While the Closure
class doesn't seem to be able to do much on its own, I can think of a few scenarios where it could really come in handy. Some of these (hypothetical) cases are:
Lazy Object Initialization
Closure
s can be useful if you want to defer initializing an object's state until the object is accessed. They are even more useful when used with LinFu
's DynamicProxy
. The dynamically generated proxies can be referenced in place of the real object while the proxy queues a list of Closure
s that will retrieve the real object's internal state. Since these closures can bind any specific value to any method argument, the proxy can queue a list of methods to call (presumably property getters/setters) that would yield the state of the actual object.
Redo and Undo Commands
For every "do" action queued, there can be an equal and opposite "undo" action queued, as well. Optionally, you can even execute the "do" action again. The Closure
class makes it really easy to keep a list of commands to execute. Its capabilities are only limited by your own imagination.
Binding and Unbinding Closure Parameters
Probably one of the most interesting features about the Closure
class is its ability to modify its arguments at any time, even after its constructor is called and the parameter values have already been assigned. Here is how you would do it:
Type returnType = typeof (int);
Type[] parameterTypes = new Type[] {typeof(int), typeof(int)};
Closure closure =
new Closure(addMethodBody, returnType, parameterTypes, 1, 1);
closure.Arguments[1] = 3;
int result = (int)closure.Invoke();
Calling Delegates Bound To Closures with Closed Arguments
Since instances of the Closure
class can have any (or all) of their parameters assigned to a particular value and since Closure
s can adapt themselves to any delegate type using the AdaptTo
(
)
method, what happens when a particular delegate type is called with a specific set of arguments? Which set of arguments will be used? Will the Closure
instance use the parameters passed to the closure when the delegate instance is called, or will it use the parameters passed to the delegate? Here's the answer:
CustomDelegate addMethodBody = delegate(object[] args)
{
int a = (int)args[0];
int b = (int)args[1];
return a + b;
};
Type returnType = typeof (int);
Type[] parameterTypes = new Type[] {typeof(int), typeof(int)};
Closure closure = new Closure(addMethodBody, returnType, parameterTypes,
3, 3);
MathOperation add = closure.AdaptTo<MathOperation>();
int result = add(1, 2);
closure.Arguments[0] = Args.Lambda;
result = add(1, 2);
closure.Arguments[1] = Args.Lambda;
result = add(1, 2);
On the surface, the add
variable might look like a standard MathOperation
delegate. In reality, however, that same delegate is actually pointing back to a Closure
instance which may (or may not) ignore the arguments that you pass to MathOperation
. Just as the Closure
did in previous examples, any Args.Lambda
argument will be replaced with the next available parameter value once Closure
is invoked. In the end, it's really up to you to decide which delegate parameters should be used and which ones should be ignored.
Coming Up in the Next Article
In Part IV of this series, I'll show you how you can use LinFu
's Simple.IoC
library to add Inversion of Control features to your application. LinFu
's IoC container is so easy to use that it takes only less than a few lines of code to have the entire container load all of an application's dependencies at once. For example, let's suppose that I have the following interface defined:
public interface IEngine
{
void Start();
void Stop();
}
Furthermore, let's suppose that I wanted to create a class implementation of IEngine
(named CarEngine
) where every time the client requests an instance of IEngine
, LinFu
's IoC container should create a completely new class instance of CarEngine
. Here is how you would define it:
[Implements(typeof(IEngine), LifecycleType.OncePerRequest)]
public class CarEngine : IEngine
{
public void Start()
{
Console.WriteLine("Engine Started");
}
public void Stop()
{
Console.WriteLine("Engine Stopped");
}
}
In your client code, all you need to do is remember where you put the assembly file for the CarEngine
class (let's call it CarEngine.dll) and tell the container to load it:
string directory = AppDomain.CurrentDomain.BaseDirectory;
IContainer container = new SimpleContainer();
Loader loader = new Loader(container);
Loader.LoadDirectory(location, "*.dll");
The Magic of Four
Believe it or not, these four lines of code are all you need to get the container up and running. Loader
will scan the assemblies in the target directory that can be loaded into the container and it will load any concrete types with ImplementsAttribute
defined. If you need to add any more dependencies to the container, the only thing that you have to do is perform a simple XCOPY
operation, copy any additional assemblies into the target folder and tell Loader
to scan the directory again. That's all there is to setting up the container. The only thing left to do at this point is use the IEngine
instance:
IEngine carEngine = container.GetService<IEngine>();
carEngine.Start();
carEngine.Stop();
Constructor and Setter Injection?
The next question that you might be asking is how Simple.IoC
goes about injecting dependencies into property setters and constructors. Suffice it to say that Simple.IoC
injects all its type dependencies from the outside and concrete types are responsible for injecting their dependencies on the inside. It's not only Inversion of Control, per se; it's also "Inversion of Injection." To find out what that means, you'll just have to wait for Part IV. Stay tuned!
History
- 2 November, 2007 -- Original version posted
- 6 November, 2007 -- Article and downloads updated
- 12 november, 2007 -- Source and binary downloads updated