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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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):
1 2 |
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:
1 2 3 4 5 |
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?
1 2 |
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:
1 2 3 4 |
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:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 |
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.
Load comments