|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn this installment of the series, I'll show you how you can use the BackgroundWith 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 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 Features and UsageThe Universal Event HandlingBelieve it or not, using // 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 All in the ReflectionThe answer is that the Event ArgumentsA 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 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 ClosuresIf 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™ ExplanationAside 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 Currying and Lambda ArgumentsIn 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 public int Add(int first, int second)
{
return first + second;
}
In a functional programming language, I can actually create custom implementations of the public int Add(int first)
{
// Functional languages allow you to derive functions (aka methods)
// from each other
int second = 1;
return first + second;
}
The CatchOther than by using Delegates and Argument BindingThe 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 // 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 Nesting Lazy Method CallsAs you probably guessed by now, 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 Instance Method BindingThe public Closure(object instance, MethodInfo targetMethod,
params object[] suppliedArguments);
Binding a // 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 DynamicObjectIf you need the same functionality without having to use reflection, then using the 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 Static Method BindingThe 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 MethodsThere 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 public Closure(CustomDelegate body, Type returnType,
Type[] parameterTypes, params object[] suppliedArguments);
Using this constructor, I can create a strongly-typed 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 MethodsOnce the constructor is called, Adapting Closures to Any Delegate SignatureThe // 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 Where This Might Be UsefulThis feature can be useful if you want to dynamically generate your own custom handlers at runtime. When combined with Points of InterestPractical Uses for ClosuresWhile the Lazy Object Initialization
Redo and Undo CommandsFor 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 Binding and Unbinding Closure ParametersProbably one of the most interesting features about the // 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 ArgumentsSince instances of the 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 Coming Up in the Next ArticleIn Part IV of this series, I'll show you how you can use public interface IEngine
{
void Start();
void Stop();
}
Furthermore, let's suppose that I wanted to create a class implementation of // 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 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 FourBelieve it or not, these four lines of code are all you need to get the container up and running. // 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 History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||