Wednesday, April 27, 2011

Beyond Duck Typing with LinFu.DynamicObject: Creating Types that can Change at Runtime

A Post-Easter Egg

One of the hidden features that LinFu.DynamicObject has is the ability to dynamically add properties and methods to itself using a shared type definition at runtime. In other words, you can have two or more LinFu.DynamicObject instances share the same DynamicType, and any changes you make to that type will be propagated to all LinFu.DynamicObject instances that share that same type:
using LinFu.Reflection.Extensions;
using NUnit.Framework;

namespace LinFu.Reflection.Tests
{
[TestFixture]
public class DynamicTypeTests
{
[Test]
public void ShouldBeAbleToShareTheSameDynamicType()
{
var typeSpec = new TypeSpec() { Name = "Person" };

// Add an age property 
typeSpec.AddProperty("Age", typeof(int));

// Attach the DynamicType named 'Person' to a bunch of dynamic objects
var personType = new DynamicType(typeSpec);
var first = new DynamicObject();
var second = new DynamicObject();

first += personType;
second += personType;

// Use both objects as persons
IPerson firstPerson = first.CreateDuck<IPerson>();
IPerson secondPerson = second.CreateDuck<IPerson>();

firstPerson.Age = 18;
secondPerson.Age = 21;

Assert.AreEqual(18, firstPerson.Age);
Assert.AreEqual(21, secondPerson.Age);

// Change the type so that it supports the INameable interface
typeSpec.AddProperty("Name", typeof(string));
INameable firstNameable = first.CreateDuck<INameable>();
INameable secondNameable = second.CreateDuck<INameable>();

firstNameable.Name = "Foo";
secondNameable.Name = "Bar";

Assert.AreEqual("Foo", firstNameable.Name);
Assert.AreEqual("Bar", secondNameable.Name);
}
}
}

Evolving Ducks

Most of the code above is self-explanatory, and the most interesting part about this code is the fact that it has two DynamicObject instances that share the same DynamicType instance. Once the Age property was added to the DynamicType definition, both first and second DynamicObjects automatically ‘inherited’ the additional Age property that was added to the DynamicType at runtime. Another interesting piece of code was the duck typing call to the IPerson interface, which wasn’t possible until after the Age property was added:

// Use both objects as persons
IPerson firstPerson = first.CreateDuck();
IPerson secondPerson = second.CreateDuck();

firstPerson.Age = 18;
secondPerson.Age = 21;

Assert.AreEqual(18, firstPerson.Age);
Assert.AreEqual(21, secondPerson.Age);


As you can see from the example above, LinFu.DynamicObject is smart enough to change its definition every time the attached DynamicType definition changes, and that’s why it was also able to duck type itself to the INameable interface:

// Change the type so that it supports the INameable interface // Change the type so that it supports the INameable interface
            typeSpec.AddProperty("Name", typeof(string));
            INameable firstNameable = first.CreateDuck<INameable>();
            INameable secondNameable = second.CreateDuck<INameable>();

            firstNameable.Name = "Foo";
            secondNameable.Name = "Bar";

            Assert.AreEqual("Foo", firstNameable.Name);
            Assert.AreEqual("Bar", secondNameable.Name);
Pretty straightforward, isn't it? (LinFu.DynamicObject has had this feature for well over 4 years, but I never got around to publishing it until now). Enjoy!

No comments:

Post a Comment

Ratings by outbrain