Dlaczego istnieją alokacje pamięci podczas wywoływania func

Mam następujący program, który konstruuje lokalny Func z dwóch statycznych metod. Ale o dziwo, kiedy profilowałem program, przydzielił blisko milion obiektów Func. Dlaczego wywołanie obiektu Func powoduje również utworzenie instancji Func?

Tutaj wpisz opis obrazka

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);
        }
    }
}
Author: Xiaoguo Ge, 2018-03-15

1 answers

Używasz konwersja grupy metod do utworzenia Func<long, long, bool> użytego dla parametru comparerFunc. Niestety, Specyfikacja C # 5 obecnie wymaga , aby utworzyć nową instancję delegata za każdym razem, gdy jest uruchamiana. W C# 5 Specyfikacja sekcja 6.6, opisujący run-time ewaluacji metody konwersji grupy:

Przydzielana jest nowa instancja delegata typu D. Jeśli nie ma wystarczającej ilości pamięci do przydzielenia nowej instancji, a System.OutOfMemoryException jest wyrzucany i nie są wykonywane żadne dalsze kroki.

Sekcja konwersji funkcji anonimowych (6.5.1) zawiera:

Konwersje identycznych semantycznie funkcji anonimowych z tym samym (prawdopodobnie pustym) zestawem przechwyconych zewnętrznych instancji zmiennych do tych samych typów delegatów są dozwolone (ale nie wymagane), aby zwrócić tę samą instancję delegata.

... ale nie ma nic podobnego do grupy metod konwersje.

Oznacza to, że ten kod jest dozwolony do optymalizacji w celu użycia pojedynczej instancji delegata dla każdego z zaangażowanych delegatów - i robi to Roslyn.

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

Inną opcją byłoby przydzielenie Func<long, long, bool> raz i zapisanie go w zmiennej lokalnej. Ta zmienna lokalna musi być przechwycona przez wyrażenie lambda, co zapobiega buforowaniu Func<Guid[], int> - co oznacza, że jeśli wykonasz Main wiele razy, utworzysz dwa nowe delegaty dla każdego wywołania, podczas gdy wcześniejsze rozwiązanie byłoby buforowane, o ile jest to uzasadnione. Kod jest jednak prostszy:

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);
}

To wszystko mnie zasmuca, a w najnowszej edycji standardu ECMA C# kompilator będzie dopuszczony do buforowania wyniku konwersji grup metod. Nie wiem kiedy/czy to to jednak zrobi.

 52
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 13:23:12