c# - 为什么要在 List<T>.AddRange(IEnumerable<T>) 中添加额外的副本?

标签 c# .net performance generics generic-list

我正在查看 System.Collections.Generic.List<T> 的开源代码. AddRange(IEnumerable<T>)方法如下所示:

public void AddRange(IEnumerable<T> collection) {
     Contract.Ensures(Count >= Contract.OldValue(Count));
 
     InsertRange(_size, collection);
}

InsertRange(int, IEnumerable<T>)方法看起来像这样:

public void InsertRange(int index, IEnumerable<T> collection) {
    if (collection==null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    }
        
    if ((uint)index > (uint)_size) {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
    }
    Contract.EndContractBlock();

    ICollection<T> c = collection as ICollection<T>;
    if( c != null ) {
        int count = c.Count;
        if (count > 0) {
            EnsureCapacity(_size + count);
            if (index < _size) {
                Array.Copy(_items, index, _items, index + count, _size - index);
            }
                
            if (this == c) {
                Array.Copy(_items, 0, _items, index, index);
                Array.Copy(_items, index+count, _items, index*2, _size-index);
            }
            else {
                T[] itemsToInsert = new T[count];        // WHY?
                c.CopyTo(itemsToInsert, 0);              // WHY?
                itemsToInsert.CopyTo(_items, index);     // WHY?

                // c.CopyTo(_items, index);              // WHY NOT THIS INSTEAD???
            }
            _size += count;
        }             
    }
    else {
        using(IEnumerator<T> en = collection.GetEnumerator()) {
            while(en.MoveNext()) {
                Insert(index++, en.Current);                                    
            }                
        }
    }
    _version++;            
}

假设我们这样调用:

var list1 = new List<int> {0, 1, 2, 3};
var list2 = new List<int> {4, 5, 6, 7};
list1.AddRange(list2);

当它到达 InsertRange(int, IEnumerable<T>) 时在内部,它最终会遇到 // WHY? 突出显示的 else 条件评论。

为什么分配一个数组,元素来自list2被复制到该临时数组中,然后元素从该临时数组复制到 list1 的末尾?为什么额外的副本?为什么不直接从 list2 复制元素呢?到 list1 结束使用 ICollection<T>.CopyTo()方法?


编辑

我会恭敬地提出,虽然这个问题是由我们这些一开始没有编写代码的人猜测的,但仍然这个问题的答案。代码以这种方式编写是有原因的,我希望有这方面知识的人可以提供解释,即使只是出于历史目的。

最佳答案

正如@OlivierJacot-Descombes 在评论部分指出的那样,当前版本的 List.cs 中删除了有问题的额外副本。在 .NET Core 库 (CoreFX) 中并替换为单一副本版本,即 c.CopyTo(_items, index); .

感谢@elgonzo 和@IvanStoev 提供发人深省的评论。总结一下,这可能只是 List<T> 进化的产物。代表了开发时的最佳选择,但这只是一个猜测。

关于c# - 为什么要在 List<T>.AddRange(IEnumerable<T>) 中添加额外的副本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54834588/

相关文章:

c# - 在具有不同坐标轴的系统之间转换欧拉角(Unity 和 Threejs)

c# - 声明一个空的 Queryable?

c# - 如何通过鼠标和按键事件确定 TreeView 上的节点

c# - Parallel.For 与常规线程

c# - readonly 关键字不会使 List<> 只读?

c# - 检查文件是否正在使用 - 附加问题

c# - 需要一个带有事件的集合类

来自 MTA 公寓的 .NET STA Com 对象

python - 提高 Cronbach Alpha 代码 python numpy 的性能

java - 这是一个意外错误。请提交包含 idea.log 文件的错误