Tuesday, April 26, 2011

Duck Typing with LinFu & C# 4.0’s Dynamic Keyword

The Lame Duck

When C# 4.0 came out with the dynamic keyword, I was pretty excited over the prospect of having Ruby-like features finally being baked in to the C# language itself, but as one of my twitter followers and friends pointed out in one of their posts from a few years ago, C# 4.0 still lacks duck typing support, and the .NET BCL doesn’t seem to have anything that could do something similar to the following code:

public interface ICanAdd
{
int Add(int a, int b);
}
public class SomethingThatAdds
{
private ICanAdd _adder;
public SomethingThatAdds(ICanAdd adder)
{
_adder = adder;
}
public int FirstNumber { get; set; }
public int SecondNumber { get; set; }
public int AddNumbers()
{
return _adder.Add(FirstNumber, SecondNumber);
}
}

There has to be some way to construct an object at runtime and map it to an ICanAdd interface, but the problem is that the current .NET Base Class Libraries don’t seem to have a solution for this problem. As Dave Tchepak pointed out in his post, the following dynamic code will fail miserably at runtime:

public class Dynamic : DynamicObject
{
Dictionary<String, object> members = new Dictionary<string, object>();
public override bool TrySetMember(SetMemberBinder binder, object value)
{
members[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return members.TryGetValue(binder.Name, out result);
}
}
// ..and the test would look like:
[Test]
public void CannotUseDynamicAdderForAnythingUseful()
{
dynamic adder = new Dynamic();
adder.Add = new Func<int, int, int>((first, second) => first + second);
var somethingThatCanAdd = new SomethingThatAdds(adder); /* Fails here at runtime */
somethingThatCanAdd.FirstNumber = 10;
somethingThatCanAdd.SecondNumber = 20;
Assert.That(somethingThatCanAdd.AddNumbers(), Is.EqualTo(30));
}



The problem is that the runtime isn’t smart enough to figure out that there has to be a duck-typing cast to the ICanAdd interface in order to use the SomethingThatCanAdd class, and that’s where LinFu’s DynamicObject comes in handy.


If it walks and quacks like a duck, then it’s all good


LinFu.DynamicObject is flexible enough that it can let you build object instances at runtime and then ‘strongly’ duck type those object instances to any interface that matches the intended duck type. In this case, we need to find a way to build up something that can map to an ICanAdd interface instance so that it can be used by the SomethingThatAdds class:

[Test]
public void CanCreateADynamicAdder()
{
var adder = new DynamicObject();
CustomDelegate addBody = delegate(object[] args)
{
int a = (int)args[0];
int b = (int)args[1];
return a + b;
};
// Map LinFu's DynamicObject to an ICanAdd interface
var linfuDynamicObject = new DynamicObject(new object());
var returnType = typeof(int);
var parameterTypes = new Type[] { typeof(int), typeof(int) };
linfuDynamicObject.AddMethod("Add", addBody, returnType, parameterTypes);
// If it looks like a duck...
Assert.IsTrue(linfuDynamicObject.LooksLike<ICanAdd>());
// ...then it must be a duck, right?
var somethingThatCanAdd = new SomethingThatAdds(adder.CreateDuck<ICanAdd>());
somethingThatCanAdd.FirstNumber = 10;
somethingThatCanAdd.SecondNumber = 20;
Assert.AreEqual(somethingThatCanAdd.AddNumbers(), 30);
}
 The call to the DynamicObject.CreateDuck() method does all the heavy lifting for you so you don’t have to worry about the details of how to make the object behave like a duck. It just works, and that’s the power that LinFu offers.
(EDIT: You can grab the source code and examples for LinFu.DynamicObject here at Github)
 
Technorati Tags: ,,

3 comments:

  1. thats great!

    side note: cannot read your code snippets that well. having to copy paste to read them... same in ff4, chrome & safari.

    ReplyDelete
  2. Thanks, Steve. Sorry for the formatting--I'm still experimenting with using different code snippet plugins for Windows LiveWriter. In any case, if you need to take a look at the code samples, you can find them in all their entirety at my github page for LinFu.DynamicObject:

    http://github.com/philiplaureano/LinFu.DynamicObject

    ReplyDelete
  3. Hi Philip, I think you would be interested, I have an open source framework that makes your failing c# 4.0 example work with one line of code(not including the using):

    adder = Impromptu.ActLike(adder); /*using ImpromptuInterface*/
    var somethingThatCanAdd = new SomethingThatAdds(adder); /*Now Works*/

    You can find it at:
    http://code.google.com/p/impromptu-interface/

    ReplyDelete

Ratings by outbrain