How do .NET delegates work?
Tags: c, net, clr
Published: 2017-03-07 15:28:24

---
title: How do .NET delegates work?
published: true
description: 
canonical_url: http://mattwarren.org/2017/01/25/How-do-.NET-delegates-work/
tags: C#, .NET, CLR
---

Delegates are a fundamental part of the .NET runtime and whilst you rarely create them directly, they are there _under-the-hood_ every time you use a lambda in LINQ (`=>`) or a `Func<T>`/`Action<T>` to [make your code more functional](https://blogs.msdn.microsoft.com/madst/2007/01/23/is-c-becoming-a-functional-language/). But how do they actually work and what's going in the CLR when you use them?

* * *

### IL of delegates and/or lambdas

``` csharp

    public delegate string SimpleDelegate(int x);

    class DelegateTest
    {
        static int Main()
        {
            // create an instance of the class
            DelegateTest instance = new DelegateTest();
            instance.name = "My instance";

            // create a delegate
            SimpleDelegate d1 = new SimpleDelegate(instance.InstanceMethod);

            // call 'InstanceMethod' via the delegate (compiler turns this into 'd1.Invoke(5)')
            string result = d1(5); // returns "My instance: 5"
        }

        string InstanceMethod(int i)
        {
            return string.Format("{0}: {1}", name, i);
        }
    }

```

If you were to take a look at the IL of the `SimpleDelegate` class, the `ctor` and `Invoke` methods look like so:

```csharp

    [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
    public SimpleDelegate(object @object, IntPtr method);

    [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
    public virtual string Invoke(int x);

```

It turns out that this behaviour is manadated by the spec, from [ECMA 335 Standard - Common Language Infrastructure (CLI)](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf):

![](https://d1ax1i5f2y3x71.cloudfront.net/items/173L2w0x1l2l082P1O1e/Screen%20Shot%202017-03-06%20at%2011.28.14%20AM.png?X-CloudApp-Visitor-Id=2119651)

So the internal implementation of a delegate, the part responsible for calling a method, is created by the runtime. This is because there needs to be complete control over those methods, delegates are a fundamental part of the CLR, any security issues, performance overhead or other inefficiencies would be a big problem.

Methods that are created in this way are technically know as `EEImpl` methods, from the â€˜Book of the Runtime' (BOTR) section â€˜[Method Descriptor - Kinds of MethodDescs](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/Documentation/botr/method-descriptor.md#kinds-of-methoddescs):

> **EEImpl** Delegate methods whose implementation is provided by the runtime (Invoke, BeginInvoke, EndInvoke). See [ECMA 335 Partition II - Delegates](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/Documentation/project-docs/dotnet-standards.md).

There's also more information available in these two excellent articles [.NET Type Internals - From a Microsoft CLR Perspective](https://www.codeproject.com/Articles/20481/NET-Type-Internals-From-a-Microsoft-CLR-Perspecti?fid=459323&fr=26#16) (section on â€˜Delegates') and [Understanding .NET Delegates and Events, By Practice](https://www.codeproject.com/Articles/26936/Understanding-NET-Delegates-and-Events-By-Practice#Internal) (section on â€˜Internal Delegates Representation')

* * *

## How the runtime creates delegates

### Inlining of delegate ctors

So we've seen that the runtime has responsibility for creating the bodies of delegate methods, but how is this done. It starts by wiring up the delegate constructor (ctor), as per the BOTR page on [â€˜method descriptors'](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/method-descriptor.md)

> **FCall** Internal methods implemented in unmanaged code. These are methods marked with MethodImplAttribute(MethodImplOptions.InternalCall) attribute, **delegate constructors** and tlbimp constructors.

At runtime this happens when the JIT compiles a method that contains IL code for creating a delegate. In [Compiler::fgOptimizeDelegateConstructor(..)](https://github.com/dotnet/coreclr/blob/0d04afc8f5919edcbb371c1e0c4f832f76aed09f/src/jit/flowgraph.cpp#L7031-L7167), the JIT firstly obtains a reference to the [correct delegate ctor](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/comdelegate.cpp#L3609), which in the simple case is `CtorOpened(Object target, IntPtr methodPtr, IntPtr shuffleThunk)` [(link to C# code)](https://github.com/dotnet/coreclr/blob/01a9eaaa14fc3de8f11eafa6155af8ce4e44e9e9/src/mscorlib/src/System/MulticastDelegate.cs#L622-L627), before finally wiring up the `ctor`, [inlining it if possible](https://github.com/dotnet/coreclr/blob/0d04afc8f5919edcbb371c1e0c4f832f76aed09f/src/jit/importer.cpp#L7366) for maximum performance.

### Creation of the delegate Invoke() method

But what's more interesting is the process that happens when creating the `Invoke()` method, using a technique involving â€˜stubs' of code (raw-assembly) that know how to locate the information about the target method and can jump control to it. These â€˜stubs' are actually used in a wide-variety of scenarios, for instance during [Virtual Method Dispatch](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/virtual-stub-dispatch.md#stubs) and also [by the JITter](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/method-descriptor.md#precode) (when a method is first called it hits a â€˜pre-code stub' that causes the method to be JITted, the â€˜stub' is then replaced by a call to the JITted â€˜native code').

In the particular case of delegates, these stubs are referred to as â€˜shuffle thunks'. This is because part of the work they have to do is â€˜shuffle' the arguments that are passed into the `Invoke()` method, so that are in the correct place (stack/register) by the time the â€˜target' method is called.

To understand what's going on, it's helpful to look at the following diagram taken from the BOTR page on [Method Descriptors and Precode stubs](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/method-descriptor.md#precode). The â€˜shuffle thunks' we are discussing are a particular case of a â€˜stub' and sit in the corresponding box in the diagram:

![](http://mattwarren.org/images/2017/01/Figure%203%20The%20most%20complex%20case%20of%20Precode,%20Stub%20and%20Native%20Code.png)

### How â€˜shuffle thunks' are set-up

So let's look at the code flow for the delegate we created in the sample at the beginning of this post, specifically an â€˜open' delegate, calling an instance method (if you are wondering about the difference between open and closed delegates, have a read of [â€˜Open Delegates vs. Closed Delegates'](http://blog.slaks.net/2011/06/open-delegates-vs-closed-delegates.html)).

We start off in the `impImportCall()` method, deep inside the .NET JIT, triggered when a [â€˜call' op-code](https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.call(v=vs.110).aspx) for a delegate is encountered, it then goes through the following functions:

1.  [Compiler::impImportCall(..)](https://github.com/dotnet/coreclr/blob/0d04afc8f5919edcbb371c1e0c4f832f76aed09f/src/jit/importer.cpp#L7348-L7353)
2.  [Compiler::fgOptimizeDelegateConstructor(..)](https://github.com/dotnet/coreclr/blob/0d04afc8f5919edcbb371c1e0c4f832f76aed09f/src/jit/flowgraph.cpp#L7031-L7167)
3.  [COMDelegate::GetDelegateCtor(..)](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/comdelegate.cpp#L3440-L3691)
4.  [COMDelegate::SetupShuffleThunk](https://github.com/dotnet/coreclr/blob/c5abe8c5a3d74b8417378e03f560fd54799c17f2/src/vm/comdelegate.cpp#L584-L632)
5.  [StubCacheBase::Canonicalize(..)](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/stubcache.cpp#L70-L165)
6.  [ShuffleThunkCache::CompileStub()](https://github.com/dotnet/coreclr/blob/c5abe8c5a3d74b8417378e03f560fd54799c17f2/src/vm/comdelegate.cpp#L473-L483)
7.  EmitShuffleThunk (specific assembly code for different CPU architectures)
    *   [arm](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/arm/stubs.cpp#L1534-L1716)
    *   [arm64](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/arm64/stubs.cpp#L1634-L1676)
    *   [i386](https://github.com/dotnet/coreclr/blob/375948e39cf1a946b3d8048ca51cd4e548f94648/src/vm/i386/stublinkerx86.cpp#L3989-L4240)

Below is the code from the [arm64 version](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/arm64/stubs.cpp#L1634-L1676) (chosen because it's the shortest one of the three!). You can see that it emits assembly code to fetch the real target address from `MethodPtrAux`, loops through the method arguments and puts them in the correct register (i.e. â€˜shuffles' them into place) and finally emits a tail-call jump to the target method associated with the delegate.

```cpp

    VOID StubLinkerCPU::EmitShuffleThunk(ShuffleEntry *pShuffleEntryArray)
    {
      // On entry x0 holds the delegate instance. Look up the real target address stored in the MethodPtrAux
      // field and save it in x9\. Tailcall to the target method after re-arranging the arguments
      // ldr x9, [x0, #offsetof(DelegateObject, _methodPtrAux)]
      EmitLoadStoreRegImm(eLOAD, IntReg(9), IntReg(0), DelegateObject::GetOffsetOfMethodPtrAux());
      //add x11, x0, DelegateObject::GetOffsetOfMethodPtrAux() - load the indirection cell into x11 used by ResolveWorkerAsmStub
      EmitAddImm(IntReg(11), IntReg(0), DelegateObject::GetOffsetOfMethodPtrAux());

      for (ShuffleEntry* pEntry = pShuffleEntryArray; pEntry->srcofs != ShuffleEntry::SENTINEL; pEntry++)
      {
        if (pEntry->srcofs & ShuffleEntry::REGMASK)
        {
          // If source is present in register then destination must also be a register
          _ASSERTE(pEntry->dstofs & ShuffleEntry::REGMASK);

          EmitMovReg(IntReg(pEntry->dstofs & ShuffleEntry::OFSMASK), IntReg(pEntry->srcofs & ShuffleEntry::OFSMASK));
        }
        else if (pEntry->dstofs & ShuffleEntry::REGMASK)
        {
          // source must be on the stack
          _ASSERTE(!(pEntry->srcofs & ShuffleEntry::REGMASK));

          EmitLoadStoreRegImm(eLOAD, IntReg(pEntry->dstofs & ShuffleEntry::OFSMASK), RegSp, pEntry->srcofs * sizeof(void*));
        }
        else
        {
          // source must be on the stack
          _ASSERTE(!(pEntry->srcofs & ShuffleEntry::REGMASK));

          // dest must be on the stack
          _ASSERTE(!(pEntry->dstofs & ShuffleEntry::REGMASK));

          EmitLoadStoreRegImm(eLOAD, IntReg(8), RegSp, pEntry->srcofs * sizeof(void*));
          EmitLoadStoreRegImm(eSTORE, IntReg(8), RegSp, pEntry->dstofs * sizeof(void*));
        }
      }

      // Tailcall to target
      // br x9
      EmitJumpRegister(IntReg(9));
    }

```

### Other functions that call `SetupShuffleThunk(..)`

The other places in code that also emit these â€˜shuffle thunks' are listed below. They are used in the various scenarios where a delegate is explicitly created, e.g. via `Delegate.CreateDelegate(..).

*   [COMDelegate::BindToMethod(..)](https://github.com/dotnet/coreclr/blob/7200e78258623eb889a46aa7a90818046bd1957d/src/vm/comdelegate.cpp#L881-L1099) - actual [call to `SetupShuffleThunk(..)`](https://github.com/dotnet/coreclr/blob/7200e78258623eb889a46aa7a90818046bd1957d/src/vm/comdelegate.cpp#L1019)
*   [COMDelegate::DelegateConstruct(..)](https://github.com/dotnet/coreclr/blob/7200e78258623eb889a46aa7a90818046bd1957d/src/vm/comdelegate.cpp#L1938-L2174) (ECall impl) - actual [call to `SetupShuffleThunk(..)`](https://github.com/dotnet/coreclr/blob/7200e78258623eb889a46aa7a90818046bd1957d/src/vm/comdelegate.cpp#L2052)
*   [COMDelegate::GetDelegateCtor(..)](https://github.com/dotnet/coreclr/blob/7200e78258623eb889a46aa7a90818046bd1957d/src/vm/comdelegate.cpp#L3440-L3691) - actual [call to `SetupShuffleThunk(..)`](https://github.com/dotnet/coreclr/blob/7200e78258623eb889a46aa7a90818046bd1957d/src/vm/comdelegate.cpp#L3618)

* * *

## Different types of delegates

Now that we've looked at how one type of delegate works (#2 â€˜Instance open non-virt' in the table below), it will be helpful to see the other different types that the runtime deals with. From the very informative [**DELEGATE KINDS TABLE**](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/comdelegate.cpp#L3547-L3567) in the CLR source:

<span class="compactTable">

<table>

<thead>

<tr>

<th style="text-align: left">#</th>

<th style="text-align: left">delegate type</th>

<th style="text-align: left">_target</th>

<th style="text-align: left">_methodPtr</th>

<th style="text-align: left">_methodPtrAux</th>

</tr>

</thead>

<tbody>

<tr>

<td style="text-align: left">1</td>

<td style="text-align: left">Instance closed</td>

<td style="text-align: left">â€˜this' ptr</td>

<td style="text-align: left">target method</td>

<td style="text-align: left">null</td>

</tr>

<tr>

<td style="text-align: left">2</td>

<td style="text-align: left"><strong>Instance open non-virt</strong></td>

<td style="text-align: left"><strong>delegate</strong></td>

<td style="text-align: left"><strong>shuffle thunk</strong></td>

<td style="text-align: left"><strong>target method</strong></td>

</tr>

<tr>

<td style="text-align: left">3</td>

<td style="text-align: left">Instance open virtual</td>

<td style="text-align: left">delegate</td>

<td style="text-align: left">Virtual-stub dispatch</td>

<td style="text-align: left">method id</td>

</tr>

<tr>

<td style="text-align: left">4</td>

<td style="text-align: left">Static closed</td>

<td style="text-align: left">first arg</td>

<td style="text-align: left">target method</td>

<td style="text-align: left">null</td>

</tr>

<tr>

<td style="text-align: left">5</td>

<td style="text-align: left">Static closed (special sig)</td>

<td style="text-align: left">delegate</td>

<td style="text-align: left">specialSig thunk</td>

<td style="text-align: left">target method</td>

</tr>

<tr>

<td style="text-align: left">6</td>

<td style="text-align: left">Static opened</td>

<td style="text-align: left">delegate</td>

<td style="text-align: left">shuffle thunk</td>

<td style="text-align: left">target method</td>

</tr>

<tr>

<td style="text-align: left">7</td>

<td style="text-align: left">Secure</td>

<td style="text-align: left">delegate</td>

<td style="text-align: left">call thunk</td>

<td style="text-align: left">MethodDesc (frame)</td>

</tr>

</tbody>

</table>

</span>

**Note:** The columns map to the [internal fields of a delegate](https://github.com/dotnet/coreclr/blob/b1f5c6acca00ca471818237d698baca317851b1f/src/mscorlib/src/System/Delegate.cs#L23-L38) (from `System.Delegate`)

So we've (deliberately) looked at the simple case, but the more complex scenarios all work along similar lines, just using different and more stubs/thunks as needed e.g. â€˜virtual-stub dispatch' or â€˜call thunk'.

* * *

## Delegates are special!!

As well as being responsible for creating delegates, the runtime also treats delegate specially, to enforce security and/or type-safety. You can see how this is implemented in the links below

In MethodTableBuilder.cpp:

*   [For delegates we don't allow any non-runtime implemented bodies for any of the four special methods](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/methodtablebuilder.cpp#L3341-L3352)
*   [It is not allowed for EnC (edit-and-continue) to replace one of the runtime builtin methods](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/methodtablebuilder.cpp#L6316-L6336)
*   [Don't allow overrides for any of the four special runtime implemented delegate methods](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/methodtablebuilder.cpp#L6706-L6719)

In ClassCompat.cpp:

*   [currently the only runtime implemented functions are delegate instance methods](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/classcompat.cpp#L2749-L2792)
*   [For delegates we don't allow any non-runtime implemented bodies for any of the four special methods](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/classcompat.cpp#L2869-L2880)

* * *

Discuss this post in [/r/programming](https://www.reddit.com/r/programming/comments/5q2w1t/how_do_net_delegates_work/) and [/r/csharp](https://www.reddit.com/r/csharp/comments/5q3ges/how_do_net_delegates_work/)

* * *

## Other links:

If you've read this far, good job!!

As a reward, below are some extra links that cover more than you could possibly want to know about delegates!!

### General Info:

*   [delegate keyword vs. lambda notation - Stack Overflow](http://stackoverflow.com/questions/299703/delegate-keyword-vs-lambda-notation)
*   [What is the difference between lambdas and delegates in the .NET Framework?](http://stackoverflow.com/questions/73227/what-is-the-difference-between-lambdas-and-delegates-in-the-net-framework)
*   [Why can't the jit inline the generated code?](https://news.ycombinator.com/item?id=13198805)
*   [Inline literal delegates passed to functions](https://github.com/dotnet/coreclr/issues/6737)
*   [Delegates and Events](http://csharpindepth.com/Articles/Chapter2/Events.aspx)
*   [Open Delegates vs. Closed Delegates](http://blog.slaks.net/2011/06/open-delegates-vs-closed-delegates.html)
*   [Using Windbg to answer implementation questions for yourself (Can a Delegate Invocation be Inlined?)](http://www.philosophicalgeek.com/2014/07/25/using-windbg-to-answer-implementation-questions-for-yourself-can-a-delegate-invocation-be-inlined/)
*   [[Question] Can Virtual Stub Dispatch be “inlined”?](https://github.com/dotnet/coreclr/issues/8819)
*   [BOTR - Virtual Stub Dispatch](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/virtual-stub-dispatch.md)

### Internal Delegate Info

*   [C# Delegates strike back Â· Curiosity is bliss](http://blog.monstuff.com/archives/000038.html) (mostly from a Mono P.O.V)
*   [Open delegate for generic interface method](http://stackoverflow.com/questions/7136615/open-delegate-for-generic-interface-method) (Bug in the CLR)
*   [[ARM/Linux] ARM-softfp delegate code generation issue](https://github.com/dotnet/coreclr/issues/5275)
*   [On x86 are four possible kinds of callsites when you take into account all features](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/virtualcallstub.h#L122-L134)
*   [VirtualCallStubManager is the heart of the stub dispatch logic. See the book of the runtime entry](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/virtualcallstub.h#L179-L222)
*   [StubDispatchNotes](https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/virtualcallstub.h#L912-L948)
*   [Anatomy of a function call in Go](https://syslog.ravelin.com/anatomy-of-a-function-call-in-go-f6fc81b80ecc)

### Debugging delegates

*   [Useful .NET Delegate Internals](http://geekswithblogs.net/akraus1/archive/2012/05/20/149699.aspx)
*   [Delegate Targets from MemoScope.Net](https://github.com/fremag/MemoScope.Net/wiki/Delegate-Targets)
*   [DelegatesAnalysis.cs from MemoScope.Net](https://github.com/fremag/MemoScope.Net/blob/master/MemoScope/Modules/Delegates/DelegatesAnalysis.cs)
*   [Getting the method name of a Delegate instance](https://github.com/Microsoft/clrmd/issues/35)
*   [Get method name from delegate with WinDbg](http://stackoverflow.com/questions/3668642/get-method-name-from-delegate-with-windbg)
*   [SOS: finding the method bound to an EventHandler with WinDbg.](http://julmar.com/blog/debugging/sos-finding-the-method-bound-to-an-eventhandler-with-windbg/)
*   [.NET Just in Time Compilation and Warming up Your System](https://blogs.msdn.microsoft.com/abhinaba/2014/09/29/net-just-in-time-compilation-and-warming-up-your-system/)

</div>

----

The post [How do .NET delegates work?](http://mattwarren.org/2017/05/25/Lowering-in-the-C-Compiler/) appeared first on my blog [mattwarren.org](http://mattwarren.org)