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





5.00/5 (1 vote)
How to create 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>();
IPerson secondPerson = second.CreateDuck<iperson>();
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
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!