|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn this installment of the series, I'll show you how you can use BackgroundSince the version 1.0 release of the .NET Framework, many developers have been clamoring for features such as multiple inheritance and mixins. Unfortunately, due to the complexity of implementing multiple inheritance, mixins and the trouble those features entail, Microsoft left those features out of the .NET Framework. Until recently, these feature requests have been largely ignored. However, thanks to upcoming dynamic languages like IronPython and IronRuby, Microsoft is finally starting to provide those language features that everyone else has been asking for. However, in order to use those features in your application, you have to switch your codebase to a new programming language. That might not be a feasible option if one has deadlines to meet and milestones to reach. This scenario effectively leaves us developers with two options: One can either find a workaround for the statically typed, single-inheritance limitations imposed by C#, VB.NET and the CLR, or one can take What You Need to KnowThis article assumes that you know enough about dynamic language features such as mixins, multiple dispatch and the like. It also assumes that you understand enough about dynamic proxies to make use of the library. Features and UsageOne of the most notable features about Late BindingUnlike typical method calls made by an application, Here's an example. Suppose that I had an instance of a StringWriter writer = new StringWriter();
DynamicObject dynamic = new DynamicObject(writer);
// Note that there are 18 overloads for TextWriter.WriteLine()
// Call WriteLine(string text);
dynamic.Methods["WriteLine"]("Hello, World!");
// Call Writeline(bool value);
dynamic.Methods["WriteLine"](false);
No matter what the method signature, Multiple DispatchAs I mentioned earlier, public class CollisionManager
{
// Overload #1
public void Collide(Circle circle, Square square, Triangle triangle)
{
Console.WriteLine("Overload #1 Called");
}
// Overload #2
public void Collide(Square square, Circle circle, Triangle triangle)
{
Console.WriteLine("Overload #2 Called");
}
// Overload #3
public void Collide(Triangle triangle, Square square, Circle circle)
{
Console.WriteLine("Overload #3 Called");
}
// Overload #4
public void Collide(Triangle triangle, Circle circle)
{
Console.WriteLine("Overload #4 Called");
}
}
A Collision with InsanityNow, suppose that I wanted to be able to call any one of these overloads, depending on both the order and type of each parameter, using only a single method call. Needless to say, doing something like this in a statically typed language is an outright nightmare. Fortunately, with // Wrap the CollisionManager instance
DynamicObject dynamic = new DynamicObject(new CollisionManager());
object[] list1 = new object[]{new Circle(), new Square(), new Triangle()};
object[] list2 = new object[]{new Square(), new Circle(), new Triangle()};
object[] list3 = new object[]{new Triangle(), new Square(), new Circle()};
object[] list4 = new object[]{new Circle(), new Triangle()};
// Call Overload #1
dynamic.Methods["Collide"](list1);
// Call Overload #2
dynamic.Methods["Collide"](list2);
// Call Overload #3
dynamic.Methods["Collide"](list3);
// Call Overload #4
dynamic.Methods["Collide"](list4);
As you can see from the example above, Dynamic Methods
public void AddMethod(string methodName, MulticastDelegate body);
public void AddMethod(string methodName, CustomDelegate body,
Type returnType, params Type[] parameters);
The first overload allows you to add a method using an existing delegate type (such as a delegate type that exists at compile time), while the second overload allows you to define a method with a particular signature and method body. I'll go over each example below: Using an Existing Delegate Typepublic delegate int MathOperation(int a, int b);
public class Program
{
public static void Main()
{
// Define the method body
MathOperation body = delegate(int a, int b) { return a + b; };
// Create an empty DynamicObject
// that wraps around a System.Object
DynamicObject dynamic = new DynamicObject();
dynamic.AddMethod("Add", body);
// This will return '2'
int result = (int)dynamic.Methods["Add"](1, 1);
}
}
In this example, I took an anonymous delegate of the type Using the CustomDelegate Type// Note: The differences are highlighted in bold
public class Program
{
public static void Main()
{
// Notice there isn't a preexisting
// MathOperation delegate in this example
CustomDelegate body = delegate(object[] args)
{
int a = (int)args[0];
int b = (int)args[1];
return a + b;
};
// Create an empty DynamicObject
// that wraps around a System.Object
DynamicObject dynamic = new DynamicObject();
Type[] parameters = new Type[] {typeof (int), typeof (int)};
Type returnType = typeof (int);
dynamic.AddMethod("Add", body, returnType, parameters);
// This will return '2'
int result = (int)dynamic.Methods["Add"](1, 1);
}
}
Using the The Choice is YoursOther than those differences that I just mentioned, the two overloads work the same way. It's really up to you to decide whether you prefer the flexibility of Duck TypingAnother one of public interface IMath
{
int Add(int a, int b);
}
Walking Like a DuckDrawing on the examples from the previous section, I can take the same IMath math = null;
// Make sure that the dynamic object
// can indeed act like an IMath object
int result = 0;
if(dynamic.LooksLike<IMath>())
{
math = dynamic.CreateDuck<IMath>();
result = math.Add(1, 1);
}
Every time the // This is equivalent to IMath.Add(1, 1)
return dynamic.Methods["Add"](1,1);
A Duck or Not a Duck?In addition to acting like an Mixins
Standard Object ReferencesA public interface IPerson
{
string Name { get; }
int Age { get; }
}
...and suppose that I wanted to implement that interface using instances of two hypothetical mixins named public class Nameable
{
private string _name;
public Nameable(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
}
}
public class HasAge
{
private int _age;
public HasAge(int age)
{
_age = age;
}
public int Age
{
get { return _age; }
}
}
As you can see, there's really nothing special about these two mixins. Creating an implementation of the DynamicObject dynamic = new DynamicObject();
IPerson person = null;
// This will return false
bool isPerson = dynamic.LooksLike<IPerson>();
// Implement IPerson
dynamic.MixWith(new HasAge(18));
dynamic.MixWith(new Nameable("Me"));
// Now that it's implemented, this
// will be true
isPerson = dynamic.LooksLike<IPerson>();
if (isPerson)
person = dynamic.CreateDuck<IPerson>();
// This will return "Me"
string name = person.Name;
// This will return '18'
int age = person.Age;
The above example would work for simple cases where the mixins are simply property holders, but what if a mixin had more complicated operations that needed to have access to Mixin-Aware InstancesFor more advanced scenarios, a mixin itself might need to interact with the other mixins that are a part of the current public interface IMixinAware
{
DynamicObject Self { get; set; }
}
public class MyMixin : IMixinAware
{
private DynamicObject _self;
public void DoSomething()
{
DynamicObject dynamic = Self;
// Perform some operations on the DynamicObject
IMath math = dynamic.CreateDuck<IMath>();
math.Add(1, 1);
}
public DynamicObject Self
{
get { return _self; }
set { _self = value;}
}
}
public class MathMixin : IMath
{
public int Add(int a, int b)
{
return a + b;
}
}
The next thing that you have to do is call the // ...somewhere in your program...
DynamicObject dynamic = new DynamicObject();
dynamic.MixWith(new MathMixin());
// Note: Once the MixWith() method is called,
// the DynamicObject will actually call
// IMixinAware.Self = this; on the new mixin
dynamic.MixWith(new MyMixin());
Any mixin class that implements the Combining DynamicObjectsCombining two public class GreeterMixin
{
public void Greet()
{
Console.WriteLine("Hello, World!");
}
}
public class OtherMixin
{
public void DoSomething()
{
Console.WriteLine("DoSomething() called");
}
}
class Program
{
static void Main(string[] args)
{
DynamicObject first = new DynamicObject(new GreeterMixin());
DynamicObject second = new DynamicObject(new OtherMixin());
// The two DynamicObjects can combine into a single object
DynamicObject combined = first + second;
combined.Methods["Greet"]();
combined.Methods["DoSomething"]();
}
}
When two Resolving Conflicts with Duplicate MixinsThere's no easy way to resolve this issue. In fact, this is probably one of the reasons why Microsoft left mixins and multiple inheritance out of the CLR in the first place. So, in the absence of a perfect solution, I decided to take the simplest one and apply it to public class GreeterMixin
{
private string _message;
public GreeterMixin(string message)
{
_message = message;
}
public void Greet()
{
Console.WriteLine(_message);
}
}
public static class Program
{
public static void Main()
{
GreeterMixin first = new GreeterMixin("Hello, World!");
GreeterMixin second = new GreeterMixin("Hello, CodeProject!");
DynamicObject dynamic = new DynamicObject();
dynamic.MixWith(first);
dynamic.MixWith(second);
// This will display "Hello, World!"
dynamic.Methods["Greet"]();
}
}
In this scenario, the first Points of InterestBypassing Sealed Methods and Types: "Duck Taping"One problem that has dogged many developers when using various class libraries is when the given library author seals a type and prevents its methods from being overridden. In normal circumstances (that is, in statically typed languages), this is akin to hitting a brick wall. Fortunately, this isn't the case with Create (or Find) an Interface That Looks Like Your Target TypeThe first thing that you need to do is figure out which methods or properties you will override in the target class. Then create an interface that is similar to the list of methods that you will be overriding. In this example, suppose that I wanted to override Wrap the Sealed Class Instance and Create the DuckDynamicObject dynamic = new DynamicObject(new SqlConnection());
// Note that this action doesn't actually cast it to an IDbConnection;
// it just makes it *act* like one
IDbConnection duck = dynamic.CreateDuck<IDbConnection>();
Override the DuckNext, we need to create an interceptor that wraps around the intended target method or methods. In this case, we want to wrap public class CustomInterceptor : IInvokeWrapper
{
private object _target;
public class CustomInterceptor(object target)
{
_target = target;
}
public void BeforeInvoke(InvocationInfo info)
{
if (info.TargetMethod.Name != "Open")
return;
// Add the additional behavior here
Console.WriteLine("Open Method Called!");
}
public object DoInvoke(InvocationInfo info)
{
// Call the original implementation
object result = info.TargetMethod.Invoke(_target, info.Arguments);
return result;
}
public void AfterInvoke(InvocationInfo info, object returnValue)
{
// Do nothing
}
}
Once the interceptor has been defined, the next thing to do is create a proxy for ProxyFactory factory = new ProxyFactory();
IDbConnection unsealed =
factory.CreateProxy<IDbConnection>(new CustomInterceptor(duck));
// Once the Open() method is called, it will now print "Open Method Called!"
unsealed.Open();
...and that's how you unseal a sealed type. Interceptor by DesignFor those of you who use ProxyFactory factory = new ProxyFactory();
IMath math = factory.CreateProxy<IMath>();
// Create the dynamic interceptor
DynamicObject interceptor = new DynamicObject();
// Attach it to the proxy
IProxy proxy = math as IProxy;
proxy.Interceptor = interceptor;
// Now the proxy is pointing back to the DynamicObject;
// This is where you would start dynamically adding methods
// to that DynamicObject
...
This approach makes it easy to incrementally build an interceptor implementation as the application is running. The possibilities are endless. Using DynamicObject with C# 3.0Another realm of possibilities opens up once you combine the abilities of Mixins Using Anonymous TypesNow that C# 3.0 has support for anonymous types, dynamically creating throwaway interface implementations is easy. Using an anonymous type, I can easily provide an implementation for DynamicObject dynamic = new DynamicObject();
dynamic.MixWith(new {Name="Me", Age=18});
IPerson person = dynamic.CreateDuck<IPerson>();
// Print the name and age
Console.WriteLine("Name = {0}", person.Name);
Console.WriteLine("Age = {0}", person.Age);
Shorter Anonymous Delegates with Lambda ExpressionsWhile in some ways, being able to write shorter anonymous delegates using lambda expressions is no more than syntactic sugar, using lambda expressions with public delegate int MathOperation(int a, int b);
public class Program
{
public static void Main()
{
// Define the method body
MathOperation body = (a, b)=> { return a + b; };
// Create an empty DynamicObject
// that wraps around a System.Object
DynamicObject dynamic = new DynamicObject();
dynamic.AddMethod("Add", body);
// This will return '2'
int result = (int)dynamic.Methods["Add"](1, 1);
}
}
Saving SpaceAt first, saving yourself from typing a few characters doesn't seem to be a marginal gain in efficiency, but what if the public interface IMath
{
int Add(int a, int b);
int Subtract(int a, int b);
int Multiply(int a, int b);
int Divide(int a, int b);
}
What was once one small declaration has turned itself into four and you have effectively increased the amount of items you have to type by 400%. What if you had to type even more than that? Suffice it to say, once you've gotten past ten repetitive anonymous delegate declarations, you'll be well past the point of tedium. For the sake of simplicity, however, let's stick to the four operations mentioned above. The next thing that I have to do is redefine all of the operations performed by the MathOperation add = (a, b) => { return a + b; }
MathOperation subtract = (a, b) => { return a – b; };
MathOperation multiply = (a, b)=> { return a * b; };
// Divide by zero check omitted for brevity
MathOperation divide = (a, b)=> { return a / b; };
Once again, in DynamicObject dynamic = new DynamicObject();
dynamic.AddMethod("Add", add);
dynamic.AddMethod("Subtract", subtract);
dynamic.AddMethod("Multiply", multiply);
dynamic.AddMethod("Divide", divide);
IMath math = dynamic.CreateDuck<IMath>();
// Perform all of the operations
Console.WriteLine(math.Add(1, 1));
Console.WriteLine(math.Subtract(1, 1));
Console.WriteLine(math.Multiply(1, 1));
Console.WriteLine(math.Divide(1, 1));
As you can see here, being able to use lambda expressions in place of full-length anonymous delegate declarations can save quite a lot of time and typing. As for the rest of the code above, well, if you've gone this far into the article, then everything I mentioned above is practically boilerplate. It's almost as if dynamically adding methods were nothing new, but I digress. Parsing LINQ Expression TreesWith LINQ coming up on the horizon, public class ExampleVisitor
{
...
public void DoVisit(Expression expression)
{
// Let the DynamicObject decide which method to call
DynamicObject dynamic = new DynamicObject(this);
dynamic.Methods["Visit"](expression);
}
public virtual void Visit(MethodCallExpression expression)
{
// Do something useful here
}
public virtual void Visit(BinaryExpression expression)
{
// Do something useful here
}
...
}
In this example, I wrapped LinFu.Reflection Features Still on the Drawing BoardThe Wish ListNow that you have Anonymous InterfacesOne of the problems in using Differential InterfacesHave you ever wanted to take two classes, put them side by side, and see which properties and methods they have in common? For example, let's say that I wanted to compare an ASP.NET button control with a standard Windows Forms button control. I want to be able to immediately determine which members these two classes have in common so that I can dynamically generate an interface to use for duck typing, employing those common members as part of the newly generated interface. The difference here is that I want this information dynamically generated at runtime and in memory, instead of having something that's scrawled on scraps of paper or a whiteboard that I cannot use. Type Coercion Using TypeConvertersThe general idea here is to modify Invoking Late-bound Generic Methods with Open Type Parameters
public class SomeClass
{
public void DoSomething<T>()
{
}
}
In its current state of development, Coming Up in the Next ArticleIn Part III of this series, I'll show you how you can use MathOperation add = delegate(int a, int b) { return a + b; };
// Note: this closure will only be evaluated
// once the Invoke() method is called
Closure doMath = new Closure(add, new Closure
(add, new Closure(add, 12, 9), 11), Args.Lambda);
// What's the answer?
// It's 42, of course! :)
int result = (int) doMath.Invoke(20);
...and suppose I wanted to use Button button = new Button();
CustomDelegate handler = delegate { Console.WriteLine("Button Clicked!"); };
EventBinder.BindToEvent("Click", button, handler);
...and if I really wanted to get fancy, I could even use // Derive a new closure from the Add() method that takes a single argument
// and adds '1' to it
Closure lazyMathOperation = new Closure
(dynamic.Methods["Add"], 1, Args.Lambda);
// This will return '6'
int result = (int) lazyMathOperation.Invoke(5);
One Last Word Before the Next ArticleNow that I'm effectively done discussing History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||