List<T>.InsertRange()
当我注意到一个奇怪的特性时,我正在阅读 .NET 4.0 框架中的代码。这是供参考的代码:
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 ) { // if collection is ICollection<T>
int count = c.Count;
if (count > 0) {
EnsureCapacity(_size + count);
if (index < _size) {
Array.Copy(_items, index, _items, index + count, _size - index);
}
// If we're inserting a List into itself, we want to be able to deal with that.
if (this == c) {
// Copy first part of _items to insert location
Array.Copy(_items, 0, _items, index, index);
// Copy last part of _items back to inserted location
Array.Copy(_items, index+count, _items, index*2, _size-index);
}
else {
T[] itemsToInsert = new T[count];
c.CopyTo(itemsToInsert, 0);
itemsToInsert.CopyTo(_items, index);
}
_size += count;
}
}
else {
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
Insert(index++, en.Current);
}
}
}
_version++;
}
特别要注意,_version 总是在函数结束时递增。这意味着在对 InsertRange 的任何非异常调用中,正在进行的列表枚举将失效,即使列表没有更改。 例如,以下代码抛出:
static void Main(string [] args) {
var list = new List<object>() {1, 2 };
using(var enumerator = list.GetEnumerator()) {
if(enumerator.MoveNext())
Console.WriteLine(enumerator.Current);
list.InsertRange(1, new object[]{});
if(enumerator.MoveNext()) // ** InvalidOperationException
Console.WriteLine(enumerator.Current);
}
}
修改方法以使枚举不会以这种方式无效,根本不会增加执行时间,因为代码已经检查了count
. 可以改写如下:
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 ) { // if collection is ICollection<T>
int count = c.Count;
if (count > 0) {
EnsureCapacity(_size + count);
if (index < _size) {
Array.Copy(_items, index, _items, index + count, _size - index);
}
// If we're inserting a List into itself, we want to be able to deal with that.
if (this == c) {
// Copy first part of _items to insert location
Array.Copy(_items, 0, _items, index, index);
// Copy last part of _items back to inserted location
Array.Copy(_items, index+count, _items, index*2, _size-index);
}
else {
T[] itemsToInsert = new T[count];
c.CopyTo(itemsToInsert, 0);
itemsToInsert.CopyTo(_items, index);
}
_size += count;
_version++;
}
}
else {
var inserted = false;
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
inserted = true;
Insert(index++, en.Current);
}
}
if (inserted) _version++;
}
}
IEnumerable
它的唯一缺点是额外的局部变量(可能会被 JIT 到寄存器中),工作集中可能增加 20 个字节,以及插入s时额外的 CPU 工作量无关紧要。如果需要避免额外的 bool 或循环分配,则IEnumerable
可以将 s 的插入执行为
if(en.MoveNext()) {
Insert(index++, en.Current);
_version++;
}
while(en.MoveNext()) {
Insert(index++, en.Current);
}
所以...
.NET 实现是预期的行为,还是错误?
编辑:
我意识到,如果您在一个线程上枚举同时修改另一个线程上的线程,那么您做错了什么。根据文档,这些情况下的行为是未定义的。但是,List<T>
在这些情况下,程序员是否会帮上忙并引发异常。我不是在问是否List<T>
正确遵循了文档:确实如此。我问的是它的实现方式是否不是微软的意图。
如果InsertRange()
行为符合预期,则List<T>
行为不一致。该RemoveRange()
方法仅在实际删除项目时使枚举无效:
static void Main(string [] args) {
var list = new List<object>() {1, 2 };
using(var enumerator = list.GetEnumerator()) {
if(enumerator.MoveNext())
Console.WriteLine(enumerator.Current);
list.RemoveRange(1, 0);
if(enumerator.MoveNext()) // ** Does not throw
Console.WriteLine(enumerator.Current);
}
}