【发布时间】:2022-10-17 18:25:02
【问题描述】:
我有一个关于闭包和堆分配的问题。考虑以下代码:
//ORIGINAL CODE, VERSION 1
public class Program
{
private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
public object Test(String x){
if(x == "abort") return null;
return _coll.GetOrAdd(x, (k)=> TestCallback());
}
public static object TestCallback() => null;
}
在Test 中使用了static 回调函数。而且,根据https://sharplab.io,这得到降低至(缩写):
//LOWERED CODE, VERSION 1
public class Program
{
private sealed class <>c
{
public static readonly <>c <>9 = new <>c(); // <== HELPER1 CREATION
public static Func<object, object> <>9__1_0;
internal object <Test>b__1_0(object k)
{
return TestCallback();
}
}
private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
public object Test(string x)
{
if (x == "abort")
{
return null;
}
return _coll.GetOrAdd(x, <>c.<>9__1_0 ?? (<>c.<>9__1_0 = new Func<object, object>(<>c.<>9.<Test>b__1_0))); // <== HELPER2 CREATION
}
public static object TestCallback() //==> STATIC METHOD
{
return null;
}
}
因此,编译器创建了一些辅助对象,但仅这样做一次(助手是静态的)。
现在,如果我从TestCallback 中删除static ...:
//ORIGINAL CODE, VERSION 1
public class Program
{
private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
public object Test(String x){
if(x == "abort") return null;
return _coll.GetOrAdd(x, (k)=> TestCallback());
}
public object TestCallback() => null; //==> INSTANCE METHOD
}
...降低的代码更改为:
//LOWERED CODE, VERSION 2
public class Program
{
private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
public object Test(string x)
{
if (x == "abort")
{
return null;
}
return _coll.GetOrAdd(x, new Func<object, object>(<Test>b__1_0)); // <== HELPER1 CREATION
}
public object TestCallback()
{
return null;
}
private object <Test>b__1_0(object k)
{
return TestCallback();
}
}
现在看来,new Func 是在每一个调用,如果x == "abort" 不正确(即_coll.GetOrAdd 实际调用)。
最后,如果我将Test 更改为包含callback 参数...:
//ORIGINAL CODE, VERSION 3
public class Program
{
private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
public object Test(String x, Func<object> callback){
if(x == "abort") return null;
return _coll.GetOrAdd(x, (k)=> callback());
}
}
...降低的代码更改为:
//LOWERED CODE, VERSION 3
public class Program
{
private sealed class <>c__DisplayClass1_0
{
public Func<object> callback;
internal object <Test>b__0(object k)
{
return callback();
}
}
private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
public object Test(string x, Func<object> callback)
{
<>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0(); // <== HELPER1 CREATION
<>c__DisplayClass1_.callback = callback;
if (x == "abort")
{
return null;
}
return _coll.GetOrAdd(x, new Func<object, object>(<>c__DisplayClass1_.<Test>b__0)); // <== HELPER2 CREATION
}
}
在这里,似乎创建了一个new <>c__DisplayClass1_0每一个打电话,不管x == "abort"。
总结一下:
- 版本 1:一次创建 2 个助手。
- Version2:在实际调用
_cao..GetOrAdd时创建1 个帮助程序。 - 版本 3:在每次调用时创建 2 个助手。
这个对吗?如果降低的代码是正确的(并且是实际编译器使用的),为什么new <>c__DisplayClass1_0 的创建没有在相关调用之前立即完成?
然后将防止不必要的分配。最终我想知道,如果这是一个实际的改进:
public IMetadata GetOrDefineMetadata(object key, Func<IMetadata> createCallback)
{
if (_coll.TryGetValue(key, out var result)) return result; //THIS LINE WAS INSERTED AS AN IMPROVEMENT
return _coll.GetOrAdd(key, (k) => createCallback()); // ==> WILL THIS STILL CAUSE ALLOCATIONS ON EVERY CALL?
}
【问题讨论】:
标签: c# closures heap-memory allocation