C#List <t> .ToArray表现不好?</t>

时间:2009-07-18 13:06:18

标签: c# .net performance memory

我正在使用.Net 3.5(C#),我听说C#List<T>.ToArray的性能“糟糕”,因为它为所有元素的内存副本形成一个新数组。这是真的吗?

7 个答案:

答案 0 :(得分:49)

不,那不是真的。性能很好,因为它所做的只是内存复制所有元素(*)以形成一个新数组。

当然,这取决于你所定义的“好”或“坏”表现。

(*)引用类型的引用,值类型的值。

修改

在回复您的评论时,使用Reflector是检查实施的好方法(见下文)。或者只考虑几分钟如何实现它,并相信微软的工程师不会提出更糟糕的解决方案。

public T[] ToArray()
{
    T[] destinationArray = new T[this._size];
    Array.Copy(this._items, 0, destinationArray, 0, this._size);
    return destinationArray;
}

当然,“好”或“坏”表现只相对于某些选择具有意义。如果在您的具体情况下,有一种替代技术可以更快地实现您的目标,那么您可以将性能视为“糟糕”。如果没有这样的选择,那么表现就是“好”(或“足够好”)。

编辑2

回应评论:“没有重建物体?” :

无需重建参考类型。对于值类型,将复制值,这可能会被松散地描述为重建。

答案 1 :(得分:19)

调用ToArray()

的原因
  • 如果不想修改返回的值,则将其作为数组返回会使事实更加清晰。
  • 如果调用者需要对数据执行许多非顺序访问,则可以通过List&lt;&gt;对阵列产生性能优势。
  • 如果您知道需要将返回的值传递给需要数组的第三方函数。
  • 与需要使用.NET版本1或1.1的调用函数的兼容性。这些版本没有List&lt;&gt;类型(或任何泛型类型)。

不要调用ToArray()

的原因
  • 如果调用者确实需要添加或删除元素,则列表&lt;&gt;是绝对必要的。
  • 不一定能保证性能优势,特别是如果调用者以顺序方式访问数据。还有从List&lt;&gt;转换的额外步骤。到数组,这需要处理时间。
  • 调用者总是可以将列表转换为数组。

取自here

答案 2 :(得分:4)

是的,它确实是所有元素的内存副本。这是性能问题吗?这取决于您的性能要求。

List内部包含一个数组,用于存放所有元素。如果容量不再足够列表,则阵列会增长。只要发生这种情况,列表就会将所有元素复制到一个新数组中。这种情况一直都在发生,对大多数人来说都不是性能问题。

E.g。具有默认构造函数的列表从容量16开始,当您.Add()第17个元素时,它创建一个大小为32的新数组,复制16个旧值并添加第17个。

大小差异也是ToArray()返回新数组实例而不是传递私有引用的原因。

答案 3 :(得分:1)

它在数组中创建了新的引用,但这只是该方法可以而且应该做的唯一事情......

答案 4 :(得分:1)

必须以相对的方式理解表现。将数组转换为List涉及复制数组,其成本取决于数组的大小。但是你必须将这个成本与你的程序正在做的其他事情进行比较。你是如何获得首先放入阵列的信息的?如果是通过从磁盘,网络连接或数据库读取,则内存中的数组副本不太可能对所花费的时间产生可检测的差异。

答案 5 :(得分:1)

这就是微软official documentation关于List.ToArray时间复杂度的说法

  

使用Array.Copy复制元素,这是一个O(n)操作,其中n是Count。

然后looking at Array.Copy,我们看到它通常不是克隆数据,而是使用引用:

  

如果sourceArray和destinationArray都是引用类型的数组,或者都是Object类型的数组,则执行浅表复制。数组的浅表副本是包含与原始数组相同元素的引用的新数组。不会复制元素本身或元素引用的任何内容。相反,数组的深层副本将复制元素以及元素直接或间接引用的所有内容。

因此,总而言之,这是从列表中获取数组的一种非常有效的方法。

答案 6 :(得分:0)

对于知道长度的任何类型的List / ICollection,它可以从一开始就分配一个大小合适的数组。

T[] destinationArray = new T[this._size];
Array.Copy(this._items, 0, destinationArray, 0, this._size);
return destinationArray;

如果你的源类型是IEnumerable(不是List / Collection),那么源是:

items = new TElement[4];
..
if (no more space) {
    TElement[] newItems = new TElement[checked(count * 2)];
    Array.Copy(items, 0, newItems, 0, count);
    items = newItems;

它从4号开始并以指数方式增长,每次空间耗尽时加倍。每次加倍时,都必须重新分配内存并复制数据。

如果我们知道源数据大小,我们可以避免这种轻微的开销。然而,在大多数情况下,例如阵列大小<= 1024,它将执行得如此之快,以至于我们甚至不需要考虑这个实现细节。

引用:Enumerable.cs,List.cs(F12ing into the),Joe的回答