委托 Roslyn 中的缓存行为更改

本文关键字:缓存 Roslyn 委托 | 更新日期: 2023-09-27 17:56:51

给定以下代码:

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内部的私有类中,这是我们习惯于在关闭实例变量/字段(闭包)时看到的类似行为。

我知道这是一个实施细节,可能会在任何给定时间发生变化。

我仍然想知道,将代表提升到一个新类并将其缓存在那里比简单地在调用站点缓存它有什么好处?

编辑:

这个问题讨论的行为与这里问的相同。

委托 Roslyn 中的缓存行为更改

是的。最重要的部分是包含 lambda 实现的方法现在是一个实例方法。

您可以将委托视为中间人,通过 Invoke 接收实例调用,并根据实现方法的调用约定调度该调用。

请注意,有一些平台 ABI 要求指定如何传递参数、如何返回结果、通过寄存器传递哪些参数以及在哪些寄存器中传递、如何传递"this"等等。违反这些规则可能会对依赖于堆栈遍历的工具(如调试器)产生不良影响。

现在,如果实现方法是实例方法,则委托中唯一需要发生的事情就是将"this"(调用时的委托实例)修补为封闭的 Target 对象。此时,由于其他所有内容都已位于需要的位置,因此委托可以直接跳转到实现方法主体。在许多情况下,这比实现方法是静态方法时需要执行的工作量要少得多。

我仍然想知道,将代表提升到一个新类并将其缓存在那里比简单地在调用站点缓存它有什么好处?

您错过了另一个非常重要的细节 - 它现在是一个实例方法。我相信这是这里的关键。IIRC,发现调用由实例方法"支持"的委托比调用由静态方法支持的委托更快 - 这是更改背后的动机。

这都是道听途说,在CodeMash与Dustin Campbell和Kevin Pilch-Bisson(都来自Roslyn团队)共度时光时模糊地记得,但考虑到你展示的代码,这是有道理的。

(我还没有为自己验证性能差异,听起来好像是倒退了......但是 CLR 内部可以这样有趣...