Monday, April 4, 2011

C# and the dynamic keyword

At my new job, I'm learning a lot about quite a few new technologies that I've never dealt with before, to name a few, C#, T-SQL, and FLEX.  As I've worked exclusively in the domain of C++ and Linux, this is quite a gear change for me.

One of the ways to get up to speed with C# is by perusing the book C# In a Nutshell, which is excellent.  As I was reading, I happened upon a curious component of the C# language:  the dynamic keyword.

Apparently, this keyword, simply put, forces type resolution to be deferred until runtime.  That's a bit of a hefty statement, considering that the whole point of statically typed languages (C# included) is to catch type errors at compilation time.

I have found one quasi-interesting use of this keyword.  It can do double-dispatching, though without all of the type safety.  Unfortunately, the lack of type safety causes this post to be merely interesting, not very useful.  Oh well.

Double-dispatching, if you recall, is a subset of multiple dispatching, in which two aspects of the method call are determined at run-time, rather than simply one.

Consider the following:

class MyObject {
    public virtual void MyMethod() {
       Console.WriteLine("MyObject");
    }
}
class MyObject2 : MyObject {
    public override void MyMethod() {  
       Console.WriteLine("MyObject2"); 
    }
}
 ...
 MyObject obj = new MyObject2();
 obj.MyMethod(); /// Prints "MyObject2"

Ignoring the fact that this is a nasty violation of the DRY principle (we really shouldn't be hard-coding the type of the object - we should just use GetType()), this example illustrates single dispatching.  The true type of the object obj above is determined at run-time.  This is equivalent to the following:


MyObject_MyMethod( MyObject obj )


Here, it's more obvious that this is single dispatching, because the function has one argument whose type is determined at run time.

Now double dispatching would be something like:

MyObject_MyMethod( MyObject obj, OtherPolymorphicObject other ),

whereby we could supply all the MyObject_MyMethod's we want for all permutations of MyObjects and OtherPolymorphicObjects.

The way double-dispatching is typically done in statically typed languages is via the Visitor Pattern.  This is one of the most clever patterns out there, but it is also not very useful in the real world (surprise).

The general point of the visitor pattern is to accomplish double-dispatching by making two function calls.  That is, since our language only supports single dispatching, just make two method calls, each one performing a single dispatch.  And 1+1=2.

A very simple, silly Visitor example follows.  Consider Santa's workshop.  Each toy needs to be wrapped, but needs to be wrapped differently depending on the type of toy.

    interface IWrappable {
        void AcceptWrapper(Wrapper p);
    }
    class TeddyBear : IWrappable {
        public void AcceptWrapper(Wrapper p) { p.WrapToy(this); }
    }
    class Robot : IWrappable {
        public void AcceptWrapper(Wrapper p) { p.WrapToy(this); }
    }
    class ToyCar : IWrappable {
        public void AcceptWrapper(Wrapper p) { p.WrapToy(this); }
    }
    class Wrapper
    {
        public void WrapToy(TeddyBear bear) {
            Console.WriteLine("Put in fancy bag with bow" );
        }
        public void WrapToy(Robot robot) {
            Console.WriteLine("Wrap with wrapping paper");
        } 
        public void WrapToy(ToyCar car) {
            Console.WriteLine("Take wheels off car and put in box");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Wrapper packager = new Wrapper();

            IWrappable packageable = new TeddyBear();
            packageable.AcceptWrapper(packager);
        }
    }

See how double dispatch is working here?  The TeddyBear object calls AcceptWrapper, whose sole purpose is to feed the type of the current object (TeddyBear) back into the Wrapper.

That last point is important - it's the most difficult aspect of the visitor pattern.  So I'll repeat it.

The purpose of AcceptWrapper is to feed the type of the current object (TeddyBear) back into the Wrapper.

This way, if there's no Wrapper method for a TeddyBear, a compilation error will be issued.

Now finally onto the gist of this post.  The dynamic keyword can reduce the amount of code above significantly, but also increase the potential of run-time errors, by supporting double-dispatching (really, multiple dispatching).

Observe:

    interface IWrappable {
    }
    class TeddyBear : IWrappable {
    }
    class Robot : IWrappable {
    }
    class ToyCar : IWrappable {
    }
    class Wrapper
    {
        public void WrapToy(TeddyBear bear) {
            Console.WriteLine("Put in fancy bag with bow" );
        }
        public void WrapToy(Robot robot) {
            Console.WriteLine("Wrap with wrapping paper");
        }
        public void WrapToy(ToyCar car) {
            Console.WriteLine("Take wheels off car and put in box");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Wrapper packager = new Wrapper();
            IWrappable packageable = new TeddyBear();
            packager.WrapToy((dynamic)packageable);
        }
    }
      
Now the type of the packageable object is forced to be resolved at runtime.

But then - what if there's no WrapToy function for a TeddyBear?

Little Bobby won't be getting a Teddy Bear for Christmas.  Or perhaps he'll just be getting a Teddy Bear covered with dirt and snow and reindeer hoof marks.

No comments:

Post a Comment