此处保存的分配是什么?

Don*_*Box 8 .net c#

Filip Ekberg在“ .NET异步编程入门”的“异步编程深度学习/使用附加和分离的任务”一章中,他说通过使用service异步匿名方法内部的值,它引入了闭包和不必要的分配

在此处输入图片说明

接下来,他说,这是更好地传递services作为参数的的动作代表StartNew的方法,其通过避免关闭避免了不必要的分配

在此处输入图片说明

我的问题是: 作者说通过传递servicesas参数可以节省多少分配?

为了便于调查,我举了一个简单得多的示例,并将其放在sharplab.io

  1. 没有传递参数:

    using System;
    
    public class C {
        public void M() {
            var i = 1;     
            Func<int> f = () => i + 1;
            f();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    编译为:

    public class C
    {
        [CompilerGenerated]
        private sealed class <>c__DisplayClass0_0
        {
            public int i;
    
            internal int <M>b__0()
            {
                return i + 1;
            }
        }
    
        public void M()
        {
            <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
            <>c__DisplayClass0_.i = 1;
            new Func<int>(<>c__DisplayClass0_.<M>b__0)();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    有两种分配,一种分配给生成的类,一种分配给Func<int>委托。

  2. 传递参数:

    using System;
    
    public class C {
        public void M() {
            var i = 1;        
            Func<int, int> f = (x) => x + 1;
            f(i);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    编译为:

    public class C
    {
        [Serializable]
        [CompilerGenerated]
        private sealed class <>c
        {
            public static readonly <>c <>9 = new <>c();
    
            public static Func<int, int> <>9__0_0;
    
            internal int <M>b__0_0(int x)
            {
                return x + 1;
            }
        }
    
        public void M()
        {
            int arg = 1;
            (<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Func<int, int>(<>c.<>9.<M>b__0_0)))(arg);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在这里,仍然有两种分配,一种分配给生成的类(请注意,该类中有一个静态字段创建该类的实例),另一种分配给Func<int>委托。

据我所知,在两种情况下,编译器都会生成一个类,并且有两个分配。
我可以看到的唯一区别是,在第一种情况下,生成的类具有一个成员:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public int i;
Run Code Online (Sandbox Code Playgroud)

这确实增加了分配大小,因为生成的类比第二种情况占用更多的内存。

我是否理解正确,这是作者所指的吗?

Jon*_*eet 3

区别在于缓存。

在您的原始代码中,每次调用时都会创建一个新的委托实例M()。在“聪明”版本中,每次仅创建一个实例并将其存储在静态变量中。

因此,如果只调用M()一次,就会分配相同数量的对象。如果调用M()一百万次,第一个代码分配的对象将比第二个代码分配的对象多得多。

这段代码:

(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Func<int, int>(<>c.<>9.<M>b__0_0)))(arg);
Run Code Online (Sandbox Code Playgroud)

...应该有效地解读为:

if (cachedDelegate == null)
{
    cachedDelegate = new Func<int, int>(GeneratedClass.CachedInstance.Method);
}
cachedDelegate.Invoke(arg);
Run Code Online (Sandbox Code Playgroud)

的实例<>c也会被缓存(上面称为GeneratedClass.CachedInstance) - 仅创建该实例的单个实例。(尽管这在这里不太重要,因为它只需要在创建委托时创建......我不确定在什么情况下编译器优化特别有用。)