Sean
Sean

Reputation: 62492

Memory Allocation And Caching Of Delegates

I was recently surprised by the way the compiler caches delegates when use use non-capturing delegates and lambdas. Take the following code (available at SharpLab):

using System;

public class C 
{
    static void Use(int x, int y)
    {
        // #1 - Allocates delegate per call
        SomeMethod(Add);                 
        
        // #2 - Caches delegate, doesn't allocate each time
        SomeMethod((x, y) => Add(x, y)); 
        
        // #3 - Allocates delegate per call
        SomeMethod(LocalAdd);
        
        // #4 - Caches delegate, doesn't allocate each time
        SomeMethod((a, b) => LocalAdd(a, b));
        
        static int LocalAdd(int a, int b)
        {
            return a + b;
        }
    }
    
    static int Add(int x, int y)
    {
        return x + y;
    }
    
    static int SomeMethod(Func<int, int, int> func)
    {
        return func(1, 2);
    }
}

I was surprised to see that in case #1 and #3 the compiler doesn't bother to cache the Func delegate it creates. I did wonder if the reason for #1 is down to historic reason (maybe this is how it was done before generics and lambdas), but the fact that passing the local function directly does the same suggests otherwise.

So, my question is, why doesn't the compiler cache the delegate it all cases? It seems odd that I have to use the more wordy lambda syntax in order to create a cached value. Why do I have to rewrite this:

SomeMethod(LocalAdd);

as

SomeMethod((a, b) => LocalAdd(a, b));

In order to get the compiler to cache the delegate and therefore generate less garbage?

Upvotes: 3

Views: 234

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062895

This topic has been discussed for quite some considerable time, and can be summarized as: changing it could present detectable changes that could lead to changes in behavior, and lead to bugs in code that currently works. The scenarios in which this applies are pretty niche, and relate to reference equality tests against the delegate instance.

The impacted code would be pretty negligible, but: non-zero, and usually the default is "retain compatibility", if an optimization can change behavior.

Upvotes: 3

Related Questions