Thursday, October 30, 2008

Dynamic Dispatch in C# 4.0

So C# 4.0 is finally with us (in CTP form anyway). I've just been watching Ander's PDC presentation, The Future of C#. The main focus is providing interop with dynamic languages and COM, so in order to do that they've added some dynamic goodness in C# itself. There's a new static type called dynamic :) Yes, it's the number one joke at PDC it seems. The dynamic keyword allows us to say to the compiler, "don't worry what I'm doing with this type, we're going to dispatch it at runtime".

Take a look at this code:

using System;
using Microsoft.CSharp.RuntimeBinder;
using System.Scripting.Actions;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            DoSomethingDynamic(7);
            DoSomethingDynamic(new Actor());
            DoSomethingDynamic(new DynamicThing());
            Console.ReadLine();
        }
        static void DoSomethingDynamic(dynamic thing)
        {
            try
            {
                thing.Act();
            }
            catch (RuntimeBinderException)
            {
                Console.WriteLine("thing does not implement Act");
            }
        }
    }
    public class Actor
    {
        public void Act()
        {
            Console.WriteLine("Actor.Act() was called");
        }
    }
    public class DynamicThing : IDynamicObject
    {
        public MetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new CustomMetaObject(parameter);
        }
    }
    public class CustomMetaObject : MetaObject
    {
        public CustomMetaObject(Expression parameter) : base(parameter, Restrictions.Empty){ }
        public override MetaObject Call(CallAction action, MetaObject[] args)
        {
            Console.WriteLine("A method named: '{0}' was called", action.Name);
            return this;
        }
    }
}

OK, so we've got a little console application. I've defined a method, DoSomethingDynamic, which has a parameter, 'thing', of type dynamic. We call the Act() method of thing. This is duck typing. The compiler can't check that thing has an Act method until runtime, so we're going to wrap it in a try-catch block just in case it doesn't. A side effect of duck typing is that there's no intellisense for the thing variable, we can write whatever we like against it and it will compile. Any of these would compile: thing.Foo(), thing + 56, thing.X = "hello", var y = thing[12].

Next, in the Main method, we call DoSomethingDynamic a few times, passing in different kinds of arguments. First we pass in the literal 7. Int32 doesn't have an Act method so a RuntimeBinderException is thrown. Next we pass an instance of a normal C# class, Actor. Actor has an Act method so Act is called normally as expected.The last invocation of DoSomethingDynamic shows off how you can do dynamic dispatch in C# 4.0. We define a new class called DynamicThing and have it inherit IDynamicObject. IDynamicObject has a single method you must implement: GetMetaObject. GetMetaObject returns a MetaObject and all you have to do is implement a CustomMetaObject that knows what to do with any method (or parameter, or indexer etc) invocation. Our CustomMetaObject overrides Call and simply writes the name of the method to the console. Chris Burrows from the C# compiler team has a series of three blog posts showing off these techniques here, here and here. Anders' PDC presentation, The Future of C# is here. C# is primarily a statically typed language and Anders obviously still believes that static typing is a better paradigm for large scale software. He sees the dynamic features as a way of adding capabilities to C# that have previously been the prerogative of VB and dynamic languages. He's especially contrite about the failure of C# to interoperate with COM efficiently in the past. However there are going to be lots of cases when using dynamic will be a short cut to features that are hard to implement statically. I can see the C# forums humming with complaints that intellisense doesn't work anymore, or hard to diagnose runtime errors as a result of over zealous dynamism.

The last ten minutes of the talk, when he showed us some of the post 4.0 features, was very cool. They are rewriting the C# compiler in C#. This means that the compiler API will be just another library in the framework. Applications will be able to call compiler services at runtime giving us lots of Ruby style meta-programming goodness. Tools will be able to read the C# AST, giving us incredible power for refactoring or post compilation style tweeks.

Jim Hugunin has a related presentation, Dynamic Languages in Microsoft .NET that goes deeper into the dynamic features that Anders talks about. Also well worth watching.

10 comments:

Anonymous said...

Eventually, C# will have the same features as Perl!

Mike Hadlow said...

Ha ha, very good. Greenspun's Tenth Rule: "Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp" I guess that goes for C# too :)

graffic said...

Microsoft has gone from java/C++ to python/ruby/Lisp/perl, while we had these languages available.

Now they've put everything in a soup and they've called it C# 4.0. I guess I won't try this "dish".

The next step will be to include MVC and ASP.NET in the same framework and say: Hey! we did something really cool! look!

While is a huge effort to put everything in the same place, it has happened in the past that if you enlarge your scope too much you end up with a lot of mediocre ideas and implementations.

I guess the next obfuscated code contest is gonna be in C#.

Mike Hadlow said...

Hi Graffic,

There's a very real possiblity of that. Sure you make your language very flexible, but at the same time there's a steeper learning curve for new students and a real danger of 'feature abuse'. Also, if you want to program with a dynamic type system on the CLR, use Iron****, if you want to do functional programming use F#.

Still, as a long time C# user, I'm still quite excited by the new features. I guess the dynamic stuff will be mostly used in edge-case scenarios. It's not nearly as mainstream as generics or LINQ. How many people use unsafe code and pointer arithmetic in C#?

Anonymous said...

"I guess the dynamic stuff will be mostly used in edge-case scenarios. It's not nearly as mainstream as generics or LINQ."

There's one very mainstream case where it should prove extremely useful (especially combined with them finally giving in on named/optional parameters) - COM interop.

Mike Hadlow said...

Hi Anonymous, Of course, interoperability concerns were the prime reason for the introduction of the dynamic type. I guess I mean that it'll be an edge case if you use it outside of COM or DLR interop.

joshnuss said...
This comment has been removed by the author.
joshnuss said...

seems more elegant in ruby

class Actor
  def act
    puts "Actor.act() was called"
  end
end

class DynamicThing
  def method_missing(name, *args)
    puts "A method named #{name} was called"
  end
end

def do_something_dynamic(thing)
  thing.act
rescue
  puts "thing does not implement act"
end

do_something_dynamic 7
do_something_dynamic Actor.new
do_something_dynamic DynamicThing.new

C# version: 101 lines of code
Ruby version: 21 lines of code

James said...

The code sample you posted isn't really in the spirit of what "dynamic" is for. In Sam Ng's words:

"Dynamic isn't a wildcard type that matches everything, including method signatures etc for overload resolution. It is a way to signify simply that an expression is to be bound at runtime."

So you will not be seeing "dynamic" in method or delegate signatures - or if you did, it's really functionally equivalent to using "object," which has been available since C# 1.0.

Shane said...

@joshnuss: Ruby seems no more "elegent" - apart from slight variations in syntax, they are vertually the same. And what a ridiculous thing to say...

"C# version: 101 lines of code
Ruby version: 21 lines of code"

How is that remotely relevent?

How about this:

Ruby version: 21 lines of code
C# version: 1 lines of code

using System; using Microsoft.CSharp.RuntimeBinder; using System.Scripting.Actions; using System.Linq.Expressions; namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { DoSomethingDynamic(7); DoSomethingDynamic(new Actor()); DoSomethingDynamic(new DynamicThing()); Console.ReadLine(); } static void DoSomethingDynamic(dynamic thing) { try { thing.Act(); } catch (RuntimeBinderException) { Console.WriteLine("thing does not implement Act"); } } } public class Actor { public void Act() { Console.WriteLine("Actor.Act() was called"); } } public class DynamicThing : IDynamicObject { public MetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) { return new CustomMetaObject(parameter); } } public class CustomMetaObject : MetaObject { public CustomMetaObject(Expression parameter) : base(parameter, Restrictions.Empty){ } public override MetaObject Call(CallAction action, MetaObject[] args) { Console.WriteLine("A method named: '{0}' was called", action.Name); return this; } } }