Делегирование изменений поведения кеширования в Roslyn - программирование

Делегирование изменений поведения кеширования в Roslyn

С учетом следующего кода:

public class C
{
    public void M()
    {
        var x = 5;
        Action<int> action = y => Console.WriteLine(y);
    }
}

Использование VS2013,.NET 4.5. Когда мы смотрим на декомпилированный код, мы видим, что компилятор кэширует делегат на сайте вызова:

public class C
{
    [CompilerGenerated]
    private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1;
    public void M()
    {
        if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null)
        {
            C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0);
        }
        Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1;
    }
    [CompilerGenerated]
    private static void <M>b__0(int y)
    {
        Console.WriteLine(y);
    }
}

Глядя на тот же код, декомпилированный в Roslyn (используя TryRoslyn), выдается следующий вывод:

public class C
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0
    {
        public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
        public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2;
        static <>c__DisplayClass0()
        {
            // Note: this type is marked as 'beforefieldinit'.
            C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
        }
        internal void <M>b__1(int y)
        {
            Console.WriteLine(y);
        }
    }
    public void M()
    {
        Action<int> arg_22_0;
        if (arg_22_0 = C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 =
                            new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1);
        }
    }
}

Теперь мы можем видеть, что делегат теперь переносится в закрытый класс внутри C, подобное поведение, которое мы привыкли видеть при закрытии переменной экземпляра/поля (закрытие).

Я знаю, что это деталь реализации, которая может быть изменена в любой момент времени.

И все-таки интересно, какие преимущества отнимают делегат в новый класс и кэшируют его там, просто кешируя его на сайте вызова?

Edit:

Этот вопрос говорит о том же поведении, что и здесь.

4b9b3361

Ответ 1

Да. Наиболее важная часть заключается в том, что метод, содержащий лямбда-реализацию, теперь является методом экземпляра.

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

Обратите внимание, что существуют требования к платформе ABI, которые определяют, как передаются аргументы, как возвращаются результаты, какие аргументы передаются через регистры и в каких случаях, как "this" передается и так далее. Нарушение этих правил может плохо повлиять на инструменты, которые полагаются на стекирование, например отладчики.

Теперь, если метод реализации является методом экземпляра, единственное, что должно произойти внутри делегата, - это патч "this", который является экземпляром делегата во время Invoke, быть закрытым целевым объектом. В этот момент, поскольку все остальное уже там, где оно должно быть, делегат может перейти непосредственно к телу метода реализации. Во многих случаях это заметно меньше, чем то, что нужно было бы, если бы метод реализации был статическим методом.

Ответ 2

И все-таки интересно, какие преимущества отнимают делегат в новый класс и кэшируют его там, просто кешируя его на сайте вызова?

Вы пропустили еще одну очень важную деталь - теперь это метод экземпляра. Я считаю, что ключ здесь. IIRC было установлено, что вызов делегата, который был "поддержан" методом экземпляра, был быстрее, чем вызов делегата, поддерживаемого статическим методом, что является мотивацией изменения.

Это все слухи, смутно помнившие, что проводили время с Дастином Кэмпбеллом и Кевином Пильчем-Биссоном (оба из команды Рослина) в CodeMash, но это было бы разумно с учетом кода, который вы показали.

(Я не подтвердил разницу в производительности для себя, и это звучит как обратное... но внутренние среды CLR могут быть такими забавными...)