在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/