我正在阅读一个询问 Is it better to call ToList() or ToArray() in LINQ queries? 的问题,发现自己想知道为什么 Enumerable.ToArray() 不会首先调用 Count() 方法来查找集合的大小,而不是使用动态调整自身大小的内部 Buffer{T} 类。类似于以下内容:

T[] ToArray<T>(IEnumerable<T> source)
{
    var count = source.Count();
    var array = new T[count];

    int index = 0;
    foreach (var item in source) array[index++] = item;
    return array;
}

我知道我们无法理解设计师和实现者的想法,我相信他们比我更聪明。所以问这个问题的最好方法是上面显示的方法有什么问题?它似乎减少了内存分配,并且仍然在 O(n) 时间内运行。

最佳答案

首先,Buffer<T> 类构造函数也会优化指定的序列是否可以转换为具有 ICollection 属性的 Count(如数组或列表):

TElement[] array = null;
int num = 0;
ICollection<TElement> collection = source as ICollection<TElement>;
if (collection != null)
{
    num = collection.Count;
    if (num > 0)
    {
        array = new TElement[num];
        collection.CopyTo(array, 0);
    }
}
else
    // now we are going the long way ...

因此,如果它不是集合,则必须执行查询以获取总数。但是仅使用 Enumerable.Count 来初始化正确大小的数组可能非常昂贵,而且 - 更重要的是 - 可能会产生危险的副作用。因此它是不安全的。

考虑这个简单的 File.ReadLines 示例:
var lines = File.ReadLines(path);
int count = lines.Count(); // executes the query which also disposes the underlying IO.TextReader
var array = new string[count];
int index = 0;
foreach (string line in lines) array[index++] = line;

这将抛出 ObjectDisposedException “无法从关闭的 TextReader 中读取”,因为 lines.Count() 已经执行了查询,同时读取器位于 foreach 处。

关于.net - 为什么 Enumerable<T>.ToArray() 在它可以先调用 Count() 时使用中间 Buffer?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17173215/

10-17 01:15