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