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



接下来,他说最好将services作为参数传递给StartNew方法的操作委托(delegate),该方法通过避免闭合来避免不必要的分配:



我的问题是:
作者说通过将services作为参数来保存它的分配是什么?

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

  • 不传递参数:

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

    编译为:

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

    有两种分配,一种分配给生成的类,另一种分配给Func<int>委托(delegate)。
  • 传递参数:

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

    编译为:

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

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

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

    [CompilerGenerated]
    private sealed class <>c__DisplayClass0_0
    {
        public int i;
    

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

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

    最佳答案

    区别在于缓存。

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

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

    这段代码:

    (<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Func<int, int>(<>c.<>9.<M>b__0_0)))(arg);
    

    ...应有效地理解为:
    if (cachedDelegate == null)
    {
        cachedDelegate = new Func<int, int>(GeneratedClass.CachedInstance.Method);
    }
    cachedDelegate.Invoke(arg);
    
    <>c的实例也被缓存(在上面称为GeneratedClass.CachedInstance)-仅创建一个实例。 (尽管这在这里不那么重要,因为仅在创建委托(delegate)时才需要创建它……我不确定在什么情况下编译器优化特别有用。)

    关于c# - 此处保存的分配是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58129682/

    10-17 00:17