C# 为什么枚举空数组不会在堆上分配?
考虑以下基准:C# 为什么枚举空数组不会在堆上分配?,c#,arrays,enumerator,C#,Arrays,Enumerator,考虑以下基准: [MemoryDiagnoser] public class EnumerableBenchmark { private IEnumerable<string> _emptyArray = new string[0]; private IEnumerable<string> _notEmptyArray = new string[1]; [Benchmark] public IEnumerator<string>
[MemoryDiagnoser]
public class EnumerableBenchmark
{
private IEnumerable<string> _emptyArray = new string[0];
private IEnumerable<string> _notEmptyArray = new string[1];
[Benchmark]
public IEnumerator<string> ArrayEmpty()
{
return _emptyArray.GetEnumerator();
}
[Benchmark]
public IEnumerator<string> ArrayNotEmpty()
{
return _notEmptyArray.GetEnumerator();
}
}
从结果来看,GetEnumerator
似乎在数组不为空时导致堆分配,但在数组为空时不会。我用许多不同的方法重写了基准测试,但总是得到相同的结果,所以我不认为BenchmarkDotNet是错误的
我的逻辑结论是空数组有一个缓存的枚举数。然而,该准则似乎与该理论相矛盾:
var emptyArray = new string[0];
var enum1 = emptyArray.GetEnumerator();
var enum2 = emptyArray.GetEnumerator();
Console.WriteLine("Equals: " + object.ReferenceEquals(enum1, enum2));
Console.WriteLine(enum1.GetType().Name + " - " + enum1.GetType().IsValueType);
其中显示:
Equals: False
SZArrayEnumerator - False
这件事真让我抓狂。有人知道发生了什么吗?你的假设是正确的。在提出的基准测试中,使用了枚举器的缓存版本。以下是反编译代码:
internal IEnumerator<T> GetEnumerator<T>()
{
T[] array = Unsafe.As<T[]>((object) this);
return array.Length != 0
? (IEnumerator<T>) new SZGenericArrayEnumerator<T>(array)
: (IEnumerator<T>) SZGenericArrayEnumerator<T>.Empty;
}
让我们尝试更改代码段并将数组强制转换为IEnumerable
:
可能是JIT编译器将其优化为
Array.Empty()
,问题是:当您在第二次测试中调用emptyArray.GetEnumerator()
时,您调用的是非泛型实现。请尝试((IEnumerable)emptyArray).GetEnumerator()
。(通用实现确实为空数组的枚举数使用缓存。)@jeroenmoster你说得对。在这种情况下,我们点击这个代码路径:它返回一个缓存的枚举数。嗯played@JeroenMostert啊,是的,ReferenceEquals
通过了-你应该把它作为答案发布
internal IEnumerator<T> GetEnumerator<T>()
{
T[] array = Unsafe.As<T[]>((object) this);
return array.Length != 0
? (IEnumerator<T>) new SZGenericArrayEnumerator<T>(array)
: (IEnumerator<T>) SZGenericArrayEnumerator<T>.Empty;
}
public IEnumerator GetEnumerator()
{
int lowerBound = this.GetLowerBound(0);
return this.Rank == 1 && lowerBound == 0
? (IEnumerator) new SZArrayEnumerator(this)
: (IEnumerator) new ArrayEnumerator(this, lowerBound, this.Length);
}
IEnumerable<string> emptyArray = new string[0];
var enum1 = emptyArray.GetEnumerator();
var enum2 = emptyArray.GetEnumerator();
Console.WriteLine("Equals: " + object.ReferenceEquals(enum1, enum2));
Console.WriteLine(enum1.GetType().Name + " - " + enum1.GetType().IsValueType);
Equals: True
SZGenericArrayEnumerator`1 - False