{"id":33871,"date":"2021-08-18T09:00:11","date_gmt":"2021-08-18T16:00:11","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=33871"},"modified":"2022-11-03T01:17:00","modified_gmt":"2022-11-03T08:17:00","slug":"understanding-the-cost-of-csharp-delegates","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/understanding-the-cost-of-csharp-delegates\/","title":{"rendered":"Understanding the cost of C# delegates"},"content":{"rendered":"<p><a title=\"Delegates\" href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/delegates-overview\">Delegates<\/a> are widely used in C# (and .NET, in general). Either as event handlers, callbacks, or as logic to be used by other code (as in <a title=\"Language Integrated Query (LINQ)\" href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/linq\/\">LINQ<\/a>).<\/p>\n<p>Despite their wide usage, it&#8217;s not always obvious to the developer what <a title=\"Delegate instantiation\" href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/language-specification\/delegates#delegate-instantiation\">delegate instantiation<\/a> will look like. In this post, I&#8217;m going to show various usages of delegates and what code they generate so that you can see the costs associated with using them in your code.<\/p>\n<h2>Explicit instantiation<\/h2>\n<p>Throughout the evolution of the C# language, delegate invocation has evolved with new patterns without breaking the previously existing patterns.<\/p>\n<p>Initially (versions 1.0 and 1.2), the only instantiation pattern available was the explicit invocation of the delegate type constructor with a method group:<\/p>\n<pre><code class=\"language-csharp\">delegate void D(int x);\r\n\r\nclass C\r\n{\r\n    public static void M1(int i) {...}\r\n    public void M2(int i) {...}\r\n}\r\n\r\nclass Test\r\n{\r\n    static void Main() {\r\n        D cd1 = new D(C.M1);        \/\/ static method\r\n        C t = new C();\r\n        D cd2 = new D(t.M2);        \/\/ instance method\r\n        D cd3 = new D(cd2);         \/\/ another delegate\r\n    }\r\n}<\/code><\/pre>\n<h2>Implicit conversion<\/h2>\n<p>C# 2.0 introduced <a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/language-specification\/conversions#method-group-conversions\">method group conversions<\/a> where an implicit conversion (<a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/language-specification\/conversions#implicit-conversions\">Implicit conversions<\/a>) exists from a method group (<a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/language-specification\/expressions#expression-classifications\">Expression classifications<\/a>) to a compatible delegate type.<\/p>\n<p>This allowed for short-hand instantiation of delegates:<\/p>\n<pre><code class=\"language-csharp\">delegate string D1(object o);\r\n\r\ndelegate object D2(string s);\r\n\r\ndelegate object D3();\r\n\r\ndelegate string D4(object o, params object[] a);\r\n\r\ndelegate string D5(int i);\r\n\r\nclass Test\r\n{\r\n    static string F(object o) {...}\r\n\r\n    static void G() {\r\n        D1 d1 = F;            \/\/ Ok\r\n        D2 d2 = F;            \/\/ Ok\r\n        D3 d3 = F;            \/\/ Error -- not applicable\r\n        D4 d4 = F;            \/\/ Error -- not applicable in normal form\r\n        D5 d5 = F;            \/\/ Error -- applicable but not compatible\r\n\r\n    }\r\n}<\/code><\/pre>\n<p>The assignment to <code>d1<\/code> implicitly converts the method group <code>F<\/code> to a value of type <code>D1<\/code>.<\/p>\n<p>The assignment to <code>d2<\/code> shows how it is possible to create a delegate to a method that has less derived (contravariant) parameter types and a more derived (covariant) return type.<\/p>\n<p>The assignment to <code>d3<\/code> shows how no conversion exists if the method is not applicable.<\/p>\n<p>The assignment to <code>d4<\/code> shows how the method must be applicable in its normal form.<\/p>\n<p>The assignment to <code>d5<\/code> shows how parameter and return types of the delegate and method are allowed to differ only for reference types.<\/p>\n<p>The compiler will translate the above code to:<\/p>\n<pre><code class=\"language-csharp\">delegate string D1(object o);\r\n\r\ndelegate object D2(string s);\r\n\r\ndelegate object D3();\r\n\r\ndelegate string D4(object o, params object[] a);\r\n\r\ndelegate string D5(int i);\r\n\r\nclass Test\r\n{\r\n    static string F(object o) {...}\r\n\r\n    static void G() {\r\n        D1 d1 = new D1(F);            \/\/ Ok\r\n        D2 d2 = new D2(F);            \/\/ Ok\r\n        D3 d3 = new D3(F);            \/\/ Error -- not applicable\r\n        D4 d4 = new D4(F);            \/\/ Error -- not applicable in normal form\r\n        D5 d5 = new D5(F);            \/\/ Error -- applicable but not compatible\r\n\r\n    }\r\n}<\/code><\/pre>\n<p>As with all other implicit and explicit conversions, the cast operator can be used to explicitly perform a method group conversion. Thus, this code:<\/p>\n<pre><code class=\"language-csharp\">object obj = (EventHandler)myDialog.OkClick;<\/code><\/pre>\n<p>will be converted by the compiler to:<\/p>\n<pre><code class=\"language-csharp\">object obj = new EventHandler(myDialog.OkClick);<\/code><\/pre>\n<p>This instantiation pattern might create a performance issue in loops or frequently invoke code.<\/p>\n<p>This innocent looking code:<\/p>\n<pre><code class=\"language-csharp\">static void Sort(string[] lines, Comparison&lt;string&gt; comparison)\r\n{\r\n    Array.Sort(lines, comparison);\r\n}\r\n\r\n...\r\nSort(lines, StringComparer.OrdinalIgnoreCase.Compare);\r\n...<\/code><\/pre>\n<p>Will be translated to:<\/p>\n<pre><code class=\"language-csharp\">static void Sort(string[] lines, Comparison&lt;string&gt; comparison)\r\n{\r\n    Array.Sort(lines, comparison);\r\n}\r\n\r\n...\r\nSort(lines, new Comparison&lt;string&gt;(StringComparer.OrdinalIgnoreCase.Compare));\r\n...<\/code><\/pre>\n<p>Which means that an instance of the delegate will be created on every invocation. A delegate instance that will have to be later collected by the garbage collector (GC).<\/p>\n<p>One way to avoid this repeated instantiation of delegates is to pre-instantiate it:<\/p>\n<pre><code class=\"language-csharp\">static void Sort(string[] lines, Comparison&lt;string&gt; comparison)\r\n{\r\n    Array.Sort(lines, comparison);\r\n}\r\n\r\n...\r\nprivate static Comparison&lt;string&gt; OrdinalIgnoreCaseComparison = StringComparer.OrdinalIgnoreCase.Compare;\r\n...\r\n\r\n...\r\nSort(lines, OrdinalIgnoreCaseComparison);\r\n...<\/code><\/pre>\n<p>Which will be translated by the compiler to:<\/p>\n<pre><code class=\"language-csharp\">static void Sort(string[] lines, Comparison&lt;string&gt; comparison)\r\n{\r\n    Array.Sort(lines, comparison);\r\n}\r\n\r\n...\r\nprivate static Comparison&lt;string&gt; OrdinalIgnoreCaseComparison = new Comparison&lt;string&gt;(StringComparer.OrdinalIgnoreCase.Compare);\r\n...\r\n\r\n...\r\nSort(lines, OrdinalIgnoreCaseComparison);\r\n...<\/code><\/pre>\n<p>Now, only one instance of the delegate will be created.<\/p>\n<h2>Anonymous functions<\/h2>\n<p>C# 2.0 also introduced the concept of <a title=\"Anonymous functions (C# Programming Guide)\" href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/programming-guide\/statements-expressions-operators\/anonymous-functions\">anonymous method expressions<\/a> as a way to write unnamed inline statement blocks that can be executed in a delegate invocation.<\/p>\n<p>Like a method group, an <a title=\"anonymous function expression\" href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/language-specification\/expressions#anonymous-function-expressions\">anonymous function expression<\/a> can be implicitly converted to a compatible delegate.<\/p>\n<p>C# 3.0 introduced the possibility of declaring anonymous functions using lambda expressions.<\/p>\n<p>Being a new language concept allowed the compiler designers to interpret the expressions in new ways.<\/p>\n<p>The compiler can generate a static method and optimize the delegate creation if the expression has no external dependencies:<\/p>\n<p>This code:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, (a, b) =&gt; a + b);\r\n...<\/code><\/pre>\n<p>Will be translated to:<\/p>\n<pre><code class=\"language-csharp\">[Serializable]\r\n[CompilerGenerated]\r\nprivate sealed class &lt;&gt;c\r\n{\r\n      public static readonly &lt;&gt;c &lt;&gt;9 = new &lt;&gt;c();\r\n\r\n      public static Func&lt;int, int, int&gt; &lt;&gt;9__4_0;\r\n\r\n      internal int &lt;M&gt;b__4_0(int a, int b)\r\n      {\r\n            return a + b;\r\n      }\r\n}\r\n...\r\n\r\nint ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, &lt;&gt;c.&lt;&gt;9__4_0 ?? (&lt;&gt;c.&lt;&gt;9__4_0 = new Func&lt;int, int, int&gt;(&lt;&gt;c.&lt;&gt;9.&lt;M&gt;b__4_0)));\r\n...<\/code><\/pre>\n<p>The compiler is, now, \u201csmart\u201d enough to instantiate the delegate only on the first use.<\/p>\n<p>As you can see, the member names generated by the C# compiler are not valid C# identifiers. They are valid IL identifiers, though. The reason the compiler generates names like this is to avoid name collisions with user code. There is no way to write C# source code that will have identifiers with <code>&lt;<\/code> or <code>&gt;<\/code>.<\/p>\n<p>This optimization is only possible because the operation is a static function. If, instead, the code was like this:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nint Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, (a, b) =&gt; Add(a, b));\r\n...<\/code><\/pre>\n<p>We\u2019d be back to a delegate instantiation for every invocation:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nint Add(int a, int b) =&gt; a + b;\r\n\r\nint &lt;M&gt;b__4_0(int a, int b) =&gt; Add(a, b);\r\n\r\n...\r\nvar r = ExecuteOperation (2, 3, new Func&lt;int, int, int&gt; (&lt;M&gt;b__4_0));\r\n...<\/code><\/pre>\n<p>This is due to the operation being dependent on the instance invoking the operation.<\/p>\n<p>On the other hand, if the operation is a static function:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nstatic int Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, (a, b) =&gt; Add(a, b));\r\n...<\/code><\/pre>\n<p>The compiler is clever enough to optimize the code:<\/p>\n<pre><code class=\"language-csharp\">[Serializable]\r\n[CompilerGenerated]\r\nprivate sealed class &lt;&gt;c\r\n{\r\n      public static readonly &lt;&gt;c &lt;&gt;9 = new &lt;&gt;c();\r\n\r\n      public static Func&lt;int, int, int&gt; &lt;&gt;9__4_0;\r\n\r\n      internal int &lt;M&gt;b__4_0(int a, int b)\r\n      {\r\n            return Add(a, b);\r\n      }\r\n}\r\n...\r\n\r\nint ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nstatic int Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, &lt;&gt;c.&lt;&gt;9__4_0 ?? (&lt;&gt;c.&lt;&gt;9__4_0 = new Func&lt;int, int, int&gt;(&lt;&gt;c.&lt;&gt;9.&lt;M&gt;b__4_0)));\r\n...<\/code><\/pre>\n<h3 id=\"c-11\">C# 11<\/h3>\n<p>Starting with C# 11, the compiler will reuse the delegate for static method groups.<\/p>\n<p>This code:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nstatic int Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, Add);\r\n...\r\n<\/code><\/pre>\n<p>Will now be translated to:<\/p>\n<pre><code class=\"language-csharp\">[CompilerGenerated]\r\nprivate static class &lt;&gt;O\r\n{\r\n    public static Func&lt;int, int, int&gt; &lt;0&gt;__Add;\r\n}\r\n...\r\n\r\nint ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nstatic int Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\nint num = ExecuteOperation(2, 3, &lt;&gt;O.&lt;0&gt;__Add ?? (&lt;&gt;O.&lt;0&gt;__Add = new Func&lt;int, int, int&gt;(Add)));\r\n...\r\n<\/code><\/pre>\n<h2>Closures<\/h2>\n<p>Whenever a lambda (or anonymous) expression references a value outside of the expression, a closure class will always be created to hold that value, even if the expression would, otherwise, be static.<\/p>\n<p>This code:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nstatic int Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\nvar o = GetOffset();\r\nvar r = ExecuteOperation(2, 3, (a, b) =&gt; Add(a, b) + o);\r\n...<\/code><\/pre>\n<p>Wiil cause the compiler to generate this code:<\/p>\n<pre><code class=\"language-csharp\">[CompilerGenerated]\r\nprivate sealed class &lt;&gt;c__DisplayClass4_0\r\n{\r\n      public int o;\r\n\r\n      internal int &lt;N&gt;b__0(int a, int b)\r\n      {\r\n            return Add(a, b) + o;\r\n      }\r\n}\r\n\r\nint ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nstatic int Add(int a, int b) =&gt; a + b;\r\n\r\n...\r\n&lt;&gt;c__DisplayClass4_0 &lt;&gt;c__DisplayClass4_ = new &lt;&gt;c__DisplayClass4_0();\r\n&lt;&gt;c__DisplayClass4_.o = GetOffset();\r\nExecuteOperation(2, 3, new Func&lt;int, int, int&gt;(&lt;&gt;c__DisplayClass4_.&lt;M&gt;b__0));\r\n...<\/code><\/pre>\n<p>Now, not only a new delegate will be instantiated, an instance of class to hold the dependent value. This compiler generated field to capture the variables is what is called in computer science a <a href=\"https:\/\/simple.wikipedia.org\/wiki\/Closure_%28computer_science%29\">closure<\/a>.<\/p>\n<p>Closures allow the generated function to access the variables in the scope where they were defined.<\/p>\n<p>However, by capturing the local environment or context, closure can unexpectedly hold a reference to resources that would otherwise be collected sooner causing them to be promoted to highier generations and, thus, incur in more CPU load because of the work the <a title=\"Garbage collection\" href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/garbage-collection\/\">garbage collector (GC)<\/a> needs to perform to reclaim that memory.<\/p>\n<h2>Static anonymous functions<\/h2>\n<p>Because it\u2019s very easy to write a lambda expression that starts with the intention of being static and ends up being not static, C# 9.0 introduces <a title=\"Static anonymous functions\" href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/proposals\/csharp-9.0\/static-anonymous-functions\">static anonymous functions<\/a> by allowing the <code>static<\/code> modifier to be applied to a lambda (or anonymous) expression to ensure that the expression is static:<\/p>\n<pre><code class=\"language-csharp\">var r = ExecuteOperation(2, 3, static (a, b) =&gt; Add(a, b));<\/code><\/pre>\n<p>If the same changes above are made, now the compiler will \u201ccomplain\u201d:<\/p>\n<pre><code class=\"language-csharp\">var o = GetOffset();\r\nvar r = ExecuteOperation(2, 3, static (a, b) =&gt; Add(a, b) + o); \/\/ Error CS8820: A static anonymous function cannot contain a reference to 'o'<\/code><\/pre>\n<h2>Workarounds<\/h2>\n<p>What can a developer do to avoid these unwanted instantiations?<\/p>\n<p>We\u2019ve seen what the compiler does, so, we can do the same.<\/p>\n<p>With this small change to the code:<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nint Add(int a, int b) =&gt; a + b;\r\nFunc&lt;int, int, int&gt; addDelegate;\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, addDelegate ?? (addDelegate = (a, b) =&gt; Add(a, b));\r\n...<\/code><\/pre>\n<p>The only thing the compiler will have to do now is add the delegate instantiation, but the same instance of the delegate will be used throughout the lifetime of the enclosing type.<\/p>\n<pre><code class=\"language-csharp\">int ExecuteOperation(int a, int b, Func&lt;int, int, int&gt; operation)\r\n{\r\n    return operation(a, b);\r\n}\r\n\r\nint Add(int a, int b) =&gt; a + b;\r\nFunc&lt;int, int, int&gt; addDelegate;\r\n\r\n...\r\nvar r = ExecuteOperation(2, 3, addDelegate ?? (addDelegate = new Func&lt;int, int, int&gt;((a, b) =&gt; Add(a, b)));\r\n...<\/code><\/pre>\n<h2>Closing<\/h2>\n<p>We&#8217;ve seen the different ways of using delegates and the code generated by the compiler and its side effects.<\/p>\n<p>Delegates have powerful features such as capturing local variables. And while these features can make you more productive, they aren&#8217;t free. Being aware of the differences in the generated code allows making informed decisions on what you value more for a given part of your application.<\/p>\n<p>Instantiating a delegate more frequently can incur performance penalties by allocating more memory which also increases CPU load because of the work the garbage collector (GC) needs to perform to reclaim that memory.<\/p>\n<p>For that reason, we&#8217;ve seen how we can control the code generated by the compiler in a way the best suits our performance needs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Delegates are widely used in C# (and .NET, in general), but it&#8217;s not always obvious to the developer what code they write ends up generating. In this post, I&#8217;ll show the various forms to make you aware of their costs.<\/p>\n","protected":false},"author":67439,"featured_media":33354,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,3012,756],"tags":[],"class_list":["post-33871","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-internals","category-csharp"],"acf":[],"blog_post_summary":"<p>Delegates are widely used in C# (and .NET, in general), but it&#8217;s not always obvious to the developer what code they write ends up generating. In this post, I&#8217;ll show the various forms to make you aware of their costs.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/33871","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/67439"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=33871"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/33871\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/33354"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=33871"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=33871"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=33871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}