Saturday, November 26, 2011

Dynamic class system in C# 4.0

The "dynamic" type (rather pseudo-type or keyword) is a new feature in the latest version of C# - 4.0. Its purpose is to enable an easier interoperability with dynamic type languages and COM components and is part of the new DLR system introduced in the .NET Framework 4.0. In addition to the new "dynamic" type there are two interesting new built-in .NET classes that allow you to do some sort of dynamic type programming with C# - the ExpandoObject class and the DynamicObject class. Using the "dynamic" type/keyword and the ExpandoObject you can do things like this:
dynamic sampleObject = new ExpandoObject();
sampleObject.number = 10;
sampleObject.Increment = (Action)(() => { sampleObject.number++; });
sampleObject.Increment();

You can add dynamically member variables to the "dynamic" instance and also methods by assigning delegates or anonymous methods to instance members (you can also assign and use events in the same manner). These tricks are possible and the C# compiler doesn't raise compilation errors exactly because of the "dynamic" keyword. It ensures that  these assignments, member variable usages and method invocations are not checked during compile time but all that happens at run time. The dynamic dispatching semantics are implemented by the ExpandoObject class. It implements a special .NET interface called IDynamicMetaObjectProvider which is used as a bridge by the DLR for the correct routing of all these dynamic invocations and references at run time. It is possible to create custom classes implementing this interface but it is actually easier to do that by inheriting the second class that I mentioned - the DynamicObject class. The DynamicObject class is an abstract class and if you inherit your class from it you will be able to easily implement your custom dynamic member dispatching logic by overriding one or more of its virtual methods.
I will not go into further detail about the "dynamic" type/keyword and the ExpandoObject as this is quite a broad subject and there are many in depth articles already available in MSDN. Instead I will go directly to the subject of this posting which is the simple dynamic class system for C# 4.0 that I implemented.
First, let me briefly explain as to why I came with the idea of implementing a dynamic class system in C#. The main reason was to do it just for fun and to prove (at least to myself) the expression power of C#. As a side note this means that this implementation is not intended for any serious usage, I coded the whole thing in just a couple of hours, so it lacks any performance optimizations whatsoever, it doesn't support multi threading and is not thoroughly tested. So, basically the answer to the question, why would there be any need to add dynamic classes support to a powerful static type language like C# is the plainly sounding - just to demonstrate that it is possible.
And the immediate reason to do this was that when I was reading the MSDN documentation about the ExpandoObject class I started wondering why they made it only possible to add dynamically members to a single instance/object only instead of providing support for dynamic classes with which to enable the creation of many instances having a predefined set of members simultaneously. Of course, this can be somehow emulated with the ExpandoObject itself - one only needs to create a "factory" method that creates and returns instances of the ExpandoObject "initialized" with the desired set of members but I wanted something more coherent and self-contained. And also some real class support with at least some of the standard class system features like inheritance, polymorphism, etc.
You can download the implementation of the dynamic class system from here - this is a small console application that defines two public classes - "Class" and "ClassDefinition" (quite descriptive names) which contain the whole logic of the dynamic class system. The application contains also a big demonstration section with lots of verbose comments describing the details of the usage of the dynamic classes - I will have this entire section pasted at the end of this posting, so that you can get an idea of the syntactic peculiarities of these "dynamic classes". But before doing that let me quickly list the main features of this custom dynamic class system:
  • uses only C# 4.0 syntax without any external or extra language declarations or configurations
  • supports simple class inheritance - a class can inherit only one base class
  • classes are defined with imperative statements (as opposed to the declarative syntax for normal/static C# classes)
  • supports simple polymorphism - all methods are treated as virtual
  • supports class constructors and constructor overloading
  • supports method overloading - you can have two or more member methods with the same name but with different set of parameters
  • supports the "self" keyword for referencing member variables/methods from member methods (analogue to the C#'s native "this" keyword)
  • supports the "super" keyword for referencing methods defined in the parent class (if available)
  • objects (class instances) are "open" - you can initialize and use additional member variables and methods that are not defined in the class (similarly to the objects in JavaScript and the ExpandoObject itself)
  • class definitions are also "open" - you can extend the definition of any class at any later moment adding new member variables and methods (slightly influenced by Ruby). The new methods are immediately available in all class instances even those created before the extension of the class definition.
  • relaxed rules for uninitialized member variables and methods. You can reference not initialized member variables and this will not raise an error - but the value returned will be null. You can call not initialized (not existing) methods and this will not raise an error either - the invocation will do nothing and will return null.
  • doesn't support events
  • doesn't support access modifiers - all members are treated as public and accessible from any context
And here is the demonstration section describing the "syntax" of the dynamic class system:


// 'Class' is a static class with one static method - you can define dynamic classes with it: Class.DefineClass
// you specify the class name, the base class (null for none) and an Action delegate (lambda expression) that defines the class' members
Class.DefineClass(new ClassDefinition()
{
    ClassName = "BaseClass", 
    Variables = (def) =>     {
         // two member variables
         def.Register1 = 1;
         def.Register2 = 2;
     },
     Methods = (def, self) =>     {
         // the Init method is used as a constructor
         def.Init = new Action<intint>((a, b) => { self.Register1 = a; self.Register2 = b; });
         // define an Add method
         def.Add = new Func<intintint>((a, b) => a + b);
         // define an overload of the Add method that sums the member properties Register1 and Register2 - note the usage of the "self" lambda parameter
         def.Add = new Func<int>(() => self.Register1 + self.Register2);
         def.ProxyAdd = new Func<int>(() => { Console.Write("BaseClass.ProxyAdd calling Add(): "); return self.Add(); });
     } 
});
        
// now have a look at the static property "New" of the Class class, which is of type ... dynamic
// and we can call methods on this "New" dynamic object which will return instances of our custom dynamic classes with the same name // let's create an instance of the dynamic class Test calling its constructor that accepts a string paramter
// but ... we haven't defined a class named "Test" let alone a constructor of the "Test" class
// still the local variable c1 gets initialized with a "dynamic" instance which behaves pretty much like the standard ExpandoObject class
dynamic c1 = Class.New.Test("test");
// it doesn't have any properties but we can add one
c1.var1 = "test";
// this will print "test"
Console.WriteLine(c1.var1);
// what about the "var2" property - it hasn't been initialized so c1.var2 is simply null
Console.WriteLine(c1.var2 ?? "this is null");
// we don't have any methods in c1, but we can call whatever methods we want - they will all return null (and will do nothing of course)
Console.WriteLine(c1.test(1, 2) ?? "this is null");
// let's now create an instance of the "BaseClass" class, which we have already declared. This would call the parameterless Init method of the class (if it existed)
dynamic c2 = Class.New.BaseClass();
// call its Add(int, int) method with two integer parameters - result is 2 + 2 = 4
Console.WriteLine(c2.Add(2, 2));
// call the Add() overload with no parameters - this sums the object's properties Register1 and Register2 which have their initial values: 1 + 2 = 3
Console.WriteLine(c2.Add());
// let's create another instance of the BaseClass class. This will call its Init(int, int) method that takes two integer parameters.
dynamic c3 = Class.New.BaseClass(7, 8);
// calling the parameterless Add() overload will sum the Register1 and Register2 properties which were initialized to 7 and 8 respectively: 7 + 8 = 15
Console.WriteLine(c3.Add());
// the same: 7 + 8 = 15
Console.WriteLine(c3.ProxyAdd());
// now let's define a new class that inherits the BaseClass class
Class.DefineClass(new
ClassDefinition() {
     ClassName = "DerivedClass",
     BaseClassName = "BaseClass",
     Variables = (def) =>     {
         // declare a new property
         def.Register3 = 3;
     },
     Methods = (def, self) =>     {
         // declare a constructor that accepts three integers
         // note the usage of the self.super expression - we can call methods from the base class this way (not necessary in this case since the base method accepts two parameters and the new method - three, so the new method doesn't hide the overload in the base class and it can be called simply with self.Init(int, int))
         // note that the constructor of the base class is called explicitly
         def.Init = new Action<intintint>((a, b, c) => { self.super.Init(a, b); self.Register3 = c; });
         // redefining the parameterless Add() method, this one will hide the parameterless Add() method in BaseClass - to call the base class implementation we need to use self.super.Add()
         // we call both Add overloads of BaseClass - first to sum Register1 and Register2 and then to sum the result with Register3
         def.Add = new Func<int>(() => self.super.Add(self.super.Add(), self.Register3));
     }
});
// here is an instance of the DerivedClass class (no constructor will be called since we don't have a parameterless Init() method)
dynamic c4 = Class.New.DerivedClass();
// this will invoke the DerivedClass.Add() implementation which sums Register1, Register2 and Register3: 1 + 2 + 3 = 6
Console.WriteLine(c4.Add());
// this will invoke the BaseClass.Add method(int, int) with two integer parameters (it hasn't been redefined in DerivedClass): 4 + 4 = 8
Console.WriteLine(c4.Add(4, 4));
// create an instance of DerivedClass ... using BaseClass.Init (int, int), which hasn't been redefined in DerivedClass
dynamic c5 = Class.New.DerivedClass(10, 11);
// calling DerivedClass.Add() will return: 10 + 11 + 3 = 24
Console.WriteLine(c5.Add());
// create another instance of DerivedClass - this will call Init(int, int, int) which was declared in DerivedClass
dynamic c6 = Class.New.DerivedClass(100, 200, 300);
// calling DerivedClass.Add() will return: 100 + 200 + 300 = 600
Console.WriteLine(c6.Add());
// calling BaseClass.ProxyAdd which calls Add() which is the DerivedClass.Add() version since c6 is DerivedClass
Console.WriteLine(c6.ProxyAdd());
// Declare the DerivedClass class again? No, this will only extend the definition of DerivedClass - additional members can be added. No need to specify the base class again.
Class.DefineClass(new ClassDefinition {
     ClassName = "DerivedClass",
     Variables = (def) =>      {
         // add a new member property
         def.Register4 = 4;
     },
     Methods = (def, self) =>     {
         // add a new constructor accepting 4 integer parameters - this one calls the constructor (with three integer parameters) from the first DerivedClass declaration
         def.Init = new Action<intintintint>((a, b, c, d) => { self.Init(a, b, c); self.Register4 = d; });
         // redefine again the parameterless Add method - this one will sum all properties: Register1, Register2, Register3 and Register4
         // note the tree calls to self.super.Add - two to self.super.Add(int, int) and one to self.super.Add()
         def.Add = new Func<int>(() => self.super.Add(self.super.Add(), self.super.Add(self.Register3, (self.Register4 ?? 0))));
     }
});
// the c6 instance was created before the extending of the DerivedClass definition - but it will use the new version of DerivedClass.Add()
// it doesn't have the Register4 property initialized - it's value will be null (the Add() will add it as 0)
// 100 + 200 + 300 + 0 = 600
Console.WriteLine(c6.Add());
// set Register4 explicitly
c6.Register4 = 400;
// 100 + 200 + 300 + 400 = 1000
Console.WriteLine(c6.Add());
// again 100 + 200 + 300 + 400 = 1000
Console.WriteLine(c6.ProxyAdd());
// c7 is created calling the constructor DerivedClass.Init(int, int, int) declared in the first definition of DerivedClass
dynamic c7 = Class.New.DerivedClass(1000, 2000, 3000);
// its Add method will now (as defined in the extension of DerivedClass) sum Register1, Register2, Register3 and Register4: 1000 + 2000 + 3000 + 4 = 6004
Console.WriteLine(c7.Add());
// c8 is created with the constructor DerivedClass.Init(int, int, int, int) defined in the second definition (extension) of DerivedClass
dynamic c8 = Class.New.DerivedClass(1000, 2000, 3000, 4000);
// its Add method will now sum Register1, Register2, Register3 and Register4: 1000 + 2000 + 3000 + 4000 = 10000
Console.WriteLine(c8.Add());
// we can call the parameterless constructor without the parenthesis
dynamic c9 = Class.New.DerivedClass;
// it's possible to call the Init method afterwards
c9.Init(10000, 20000);
// let's check that it's a different instance from c9 - see below
dynamic c10 = Class.New.DerivedClass;
Console.WriteLine();
// a built-in diagnostics method - dumping all member properties of the dynamic objects
Console.WriteLine(c1.dump());
Console.WriteLine(c2.dump());
Console.WriteLine(c3.dump());
Console.WriteLine(c4.dump());
Console.WriteLine(c5.dump());
Console.WriteLine(c6.dump());
Console.WriteLine(c7.dump());
Console.WriteLine(c8.dump());
Console.WriteLine(c9.dump());
Console.WriteLine(c10.dump());

1 comment:

  1. Great find, I’m going to have to check this one out. Thanks for sharing.
    Very informative….

    ReplyDelete