Click here to monitor SSC

Introduction to open instance delegates

Published 27 July 2010 6:15 pm

Creating a delegate to a method is normally quite a cheap operation. However, there are some reflection-based situations where you have to create a delegate dynamically using Delegate.CreateDelegate. As you can expect, such a method is many times slower than using the type-safe delegate constructor. Using open instance delegates can help alleviate this performance penalty.

For this post, I’ll be using these classes in the examples:

delegate void AddDelegate(int value);

class Adder {

    public Adder(int value) {
        Value = value;
    }
    
    public int Value { get; private set; }

    public void Add(int valueToAdd) {
        int result = Value + valueToAdd;
        Console.WriteLine("{0}+{1}={2}", Value, valueToAdd, result);
    }
}

Creating a delegate

The standard way of creating a delegate to an instance method is the following (I’ve expanded out the syntax for clarity):

Adder add5 = new Adder(5);
AddDelegate d = new AddDelegate(add5.Add);
This simply calls the compiler-created constructor on the AddDelegate type. In situations where you need to create the delegate dynamically, you need to use the Delegate.CreateDelegate methods instead:
Adder add5 = new Adder(5);

MethodInfo addMethodInfo = typeof(Adder).GetMethod("Add");
AddDelegate d = (AddDelegate)
    Delegate.CreateDelegate(typeof(AddDelegate), add5, addMethodInfo);
In this example, in the arguments to CreateDelegate, we’re specifying the type of delegate to create, the instance of Adder the method is to be run on, and the MethodInfo of the method to run as the delegate.

The hidden ‘this’ pointer

The reason we need to specify the firstArgument parameter to CreateDelegate is that every instance method invocation requires a reference to the object we’re calling the method on (the ‘this’ pointer) to be the first argument it is called with, before all the declared method arguments (for Add, the value parameter). What happens if we don’t specify this argument when creating the delegate?

AddDelegate d = (AddDelegate)
    Delegate.CreateDelegate(typeof(AddDelegate), null, addMethodInfo);
Not a lot it would seem; trying to invoke such a delegate throws a NullReferenceException if it’s a virtual method or accesses the ‘this’ instance (which most instance methods do). However, things get a lot more interesting if we change the delegate signature slightly:
delegate void OpenAddDelegate(Adder explicitThis, int value);

OpenAddDelegate add = (OpenAddDelegate)
    Delegate.CreateDelegate(typeof(OpenAddDelegate), null, addMethodInfo);
Here, we’re explicitly specifying the hidden ‘this’ pointer of the Add method as part of the delegate signature. A delegate to an instance method created in this way is called an open instance delegate, and by specifying the ‘this’ argument explicitly we can use this delegate instance to call the same method on multiple instances:
Adder add3 = new Adder(3);
Adder add4 = new Adder(4);

MethodInfo addMethodInfo = typeof(Adder).GetMethod("Add");
OpenAddDelegate d = (OpenAddDelegate)
    Delegate.CreateDelegate(typeof(OpenAddDelegate), null, addMethodInfo);

d(add3, 2);
d(add3, 3);
d(add4, 5);
d(add4, 6);

So?

Such a delegate may not seem immediately useful, but this can be crucial when performance is critical. Creating a delegate with CreateDelegate is an expensive operation, and if you have to call the same delegated method on many thousands of objects it can be a killer to create a new delegate instance for each object. To demonstrate this, I ran 3 tests that called a delegate of the Add method on 1,000,000 Adder objects, creating the using the normal delegate constructor, a closed CreateDelegate call for each object, and a single open instance delegate:

Delegate constructor:     0.1115875s
Closed instance delegate: 4.6705494s
Open instance delegate:   0.0621132s

Such results speak for themselves. Although rather a niche feature, when calling the same method on many thousands of separate instances, open instance delegates can improve the performance of such code many times over.

Note: This also works as expected for delegates to virtual and interface methods. You can also use the generic Func and Action delegates if you wish, rather than defining your own delegate type, as long as the type of the first argument of the delegate is compatible with the type containing the instance method you’re calling.

Leave a Reply