Почему существуют распределения памяти при вызове функции

У меня есть следующая программа, которая строит локальный Func из двух статических методов. Но как ни странно, когда я профилирую программу, она выделяет около миллиона объектов Func. Почему вызов объекта Func также создает экземпляры Func?

enter image description here

public static class Utils
{
    public static bool ComparerFunc(long thisTicks, long thatTicks)
    {
        return thisTicks < thatTicks;
    }
    public static int Foo(Guid[] guids, Func<long, long, bool> comparerFunc)
    {
        bool a = comparerFunc(1, 2);
        return 0;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Func<Guid[], int> func = x => Utils.Foo(x, Utils.ComparerFunc);
        var guids = new Guid[10];
        for (int i = 0; i < 1000000; i++)
        {
            int a = func(guids);
        }
    }
}

Ответ 1

Вы используете преобразование группы методов для создания Func<long, long, bool> используемого для параметра comparerFunc. К сожалению, в спецификации С# 5 в настоящее время требуется, чтобы каждый экземпляр делегата создавал новый экземпляр делегата. Из раздела 6.6 спецификации С# 5, описывающего оценку времени выполнения преобразования группы методов:

Выделяется новый экземпляр типа делегата. Если для размещения нового экземпляра недостаточно памяти, генерируется исключение System.OutOfMemoryException и дальнейшие шаги не выполняются.

Раздел анонимных преобразований функций (6.5.1) включает в себя следующее:

Преобразования семантически идентичных анонимных функций с одним и тем же (возможно, пустым) набором захваченных внешних экземпляров переменной для тех же типов делегатов разрешены (но не обязательно) для возврата одного и того же экземпляра делегата.

... но ничего подобного для конверсий групп методов.

Это означает, что этому коду разрешено оптимизировать использование одного экземпляра делегата для каждого из задействованных делегатов - и Roslyn.

Func<Guid[], int> func = x => Utils.Foo(x, (a, b) => Utils.ComparerFunc(a, b));

Другой вариант - выделить Func<long, long, bool> один раз и сохранить его в локальной переменной. Локальная переменная должна быть захвачена с помощью выражения лямбда, что предотвращает кэширование Func<Guid[], int> - это означает, что если вы много раз выполняли Main, вы создавали бы два новых делегата для каждого вызова, тогда как более раннее решение будет кэшировать, насколько это разумно. Код проще:

Func<long, long, bool> comparer = Utils.ComparerFunc;
Func<Guid[], int> func = x => Utils.Foo(x, comparer);
var guids = new Guid[10];
for (int i = 0; i < 1000000; i++)
{
    int a = func(guids);
}

Все это меня огорчает, и в последнем выпуске стандарта ECMA С# компилятору будет разрешено кэшировать результат преобразований группы методов. Я не знаю, когда и будет ли это делать.