Click here to Skip to main content
Click here to Skip to main content

Introducing the LinFu Framework, Part III: LinFu.Delegates-Lambda Arguments & Universal Event Handling

, 12 Nov 2007 LGPL3
Rate this:
Please Sign up or sign in to vote.
A library for currying delegates and for handling any event fired from any object instance

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 Closures, 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:

// Note: The CustomDelegate signature is defined as:
// public delegate object CustomDelegate(params object[] args);
CustomDelegate handler = delegate
                         {
                           Console.WriteLine("Button Clicked!");
                           return null;
                         };

Button myButton = new Button();
// Connect the handler to the event
EventBinder.BindToEvent("Click", myButton, handler);

As you can see from the example above, the EventBinderclassknows 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.Clickevent 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 CustomDelegateinstance.

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 EventArgsargument 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();

// Connect the handler to the event
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}!";

// Notice that this anonymous delegate is actually using
// the format from outside the anonymous delegate block
Action<string> speak = delegate(string name)
{ Console.WriteLine(format, name); };

// Say "Hello, World!"
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)
{
   // Functional languages allow you to derive functions (aka methods)
   // from each other
   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; };

       // Derive a new add method and
       // set the second parameter value = 1
       Closure addOne = new Closure(add, Args.Lambda, 1);

       // This will return '3'
       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:

// This the same as addOne.Invoke(2)
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, Closures 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);

       // Evaluate the inner closure and pass its
       // result to the outer closure
       Closure outer = new Closure(add, inner, 1);

       // This will return '3' ( 1 + 1 + 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:

// The math class is defined somewhere else
MathClass math = new MathClass();

// Call the previous Add() method
MethodInfo method = typeof (MathClass).GetMethod("Add");
Closure closure = new Closure(math, method, 1, 1);

// This will return '2'
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();

// Wrap the MathClass instance
DynamicObject dynamic = new DynamicObject(math);

// Let the DynamicObject decide which method to call, and
// give it a set of arguments to use
Closure closure = new Closure(dynamic.Methods["Add"], 1, 1);

// return '2'
int result = (int)closure.Invoke();

In this example, the DynamicObjectwill 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;
                             };

// Define the method signature
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:

// Note: The differences are highlighted in bold

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;
                         };

// Specify the method signature
Type returnType = typeof (int);
Type[] parameterTypes = new Type[] {typeof(int), typeof(int)};

// Leave both arguments open
Closure closure = new Closure(addMethodBody, returnType, parameterTypes,
    Args.Lambda, Args.Lambda);

// Make the closure appear to be a MathOperation delegate
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

Closures 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 Closures 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:

// Define the method signature

Type returnType = typeof (int);
Type[] parameterTypes = new Type[] {typeof(int), typeof(int)};

// Notice that the original parameters are 1 + 1
Closure closure = 
    new Closure(addMethodBody, returnType, parameterTypes, 1, 1);

// Change the value of the second parameter to '3'
closure.Arguments[1] = 3;

// This will return '4' instead of '2'
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 Closures 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)};

// Assign '3' to both parameters
Closure closure = new Closure(addMethodBody, returnType, parameterTypes,
                              3, 3);
MathOperation add = closure.AdaptTo<MathOperation>();

// This will return '6' (3 + 3)
int result = add(1, 2);

// Open the first parameter
closure.Arguments[0] = Args.Lambda;

// This will return '4' (3 + 1)
result = add(1, 2);

// Open both arguments
closure.Arguments[1] = Args.Lambda;

// This will return '3' (1 + 2)
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:

// This attribute tells the loader how you want the lifetime of this 

// IEngine instance to be managed
[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);

// Load any other assemblies, including CarEngine.dll
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:

// Use an IEngine instance
IEngine carEngine = container.GetService<IEngine>();

// This will print "Engine Started"
carEngine.Start();

// This will print "Engine Stopped"
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

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

Philip Laureano
Software Developer (Senior) Readify
Australia Australia
No Biography provided
Follow on   Twitter

Comments and Discussions

 
GeneralPart IV PinmemberPhilip Laureano15-Nov-07 11:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 12 Nov 2007
Article Copyright 2007 by Philip Laureano
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid