Framework Tips XII: Advanced .NET delegates

All .NET delegates inherit (indirectly) from System.Delegate class (and directly from System.MulticastDelegate) which has a static CreateDelegate method. The method has two powerful characteristics that are not widely known.

All delegate types are implemented by the runtime. What do I mean by that? Let’s have a look at how any delegate type looks at the IL level:

delegateInIL

All methods are runtime managed, meaning that, in a similar fashion you provide implementation for interface methods, runtime provides implementation for delegate methods. Take a look at the constructor. Regardless of delegate type it always has two arguments: object (on which a method will be invoked via the delegate) and a handle to that method.

While trying (with great help of Kenneth Xu) to find a way of providing proxying of explicitly implemented interface members in Dynamic Proxy, I saw this as a possible way of being able to implement the feature. The problem in this case is, that proxy is an inherited class, that has to call a private method on the base class. So given that Dynamic Proxy works in IL, I tried to call that constructor in IL passing required parameters, but unsurprisingly, runtime does access checks so what I got instead of working delegate was this:

MethodAccessException

 

 

Kenneth suggested another approach – using Delegate.CreateDelegate, because as it turns out, you can successfully use that method to bind delegates to private methods. That is the first important characteristic that may be a life saver in certain cases.

So, instead of this (which won’t compile):

public override int BarMethod(int num1)
{
    Func<int, int> d = new Func<int, int>(this.DynamicProxy.Program.IFoo.BarMethod);
    return d(num1);
}

You can use this, which will both compile and work:

public override int BarMethod(int num1)
{
    Func<int, int> d = Delegate.CreateDelegate(typeof(Func<int, int>), this, barMethodInfo) as Func<int, int>;
    return d(num1);
}

It has a drawback though – it’s order of magnitude slower than creating the delegate via its constructor. Solution brings us to the second characteristic I wanted to talk about – open instance methods.

Each instance method at IL level has an additional argument, implicitly passed this pointer. Armed with this knowledge, let’s read what MSDN says about Delegate.CreateDelegate and open instance methods:

If firstArgument is a null reference and method is an instance method, the result depends on the signatures of the delegate type type and of method:

  • If the signature of type explicitly includes the hidden first parameter of method, the delegate is said to represent an open instance method. When the delegate is invoked, the first argument in the argument list is passed to the hidden instance parameter of method.

  • If the signatures of method and type match (that is, all parameter types are compatible), then the delegate is said to be closed over a null reference. Invoking the delegate is like calling an instance method on a null instance, which is not a particularly useful thing to do.

This basically says that we can bind method to delegate that passes the ‘this’ argument explicitly, so that we can reuse the delegate for multiple instances. Here’s how that works:

private static Func<Foo, int, int> d;
 
static Foo()
{
    d = Delegate.CreateDelegate(typeof(Func<Foo, int, int>), barMethodInfo) as Func<Foo, int, int>;
}
 
public override int BarMethod(int num1)
{
    return d(this, num1);
}

With this approach we pay the price of delegate creation only once per lifetime of the type.

I was curious how all these approaches would perform, so I created a quick poor-man’s benchmark.

I invoked an instance method 100 times using four approaches:

  1. directly
  2. via delegate created using its constructor
  3. via delegate created using CreateDelegate passing first argument explicitly (not null).
  4. via delegate created once using CreateDelegate and then reusing it for subsequent calls.

Here are the results (times are in ticks):

poormansDelegateBenchmark

As you can see, when reusing the instance after 100 calls we can achieve the same performance as when using the delegate’s constructor, which, given we can use in broader spectrum of cases, is pretty amazing.

If you want to run the tests for yourself, the code is here.

Technorati Tags: ,,,