gpt4 book ai didi

c# - List.InsertRange() 或设计决策中的错误?

转载 作者:行者123 更新时间:2023-11-30 16:24:07 25 4
gpt4 key购买 nike

我正在阅读 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++;
}
}

它的唯一缺点是一个额外的局部变量(可能会被 JIT 到一个寄存器中),工作集可能增加 20 个字节,以及插入 IEnumerable 时不相关的额外 CPU 工作量。秒。如果需要避免额外的 bool 或循环内赋值,则插入 IEnumerable s 可以执行为

if(en.MoveNext()) {
Insert(index++, en.Current);
_version++;
}

while(en.MoveNext()) {
Insert(index++, en.Current);
}

所以...

.NET 实现是预期行为,还是错误?

编辑:

我意识到,如果您在一个线程上进行枚举,而在另一个线程上修改线程,那么您就做错了。根据文档,这些情况下的行为是未定义的。然而,List<T>帮程序员一个忙,并在这些情况下抛出异常。我不是在问 List<T>正确遵循文档:确实如此。我想问的是它的实现方式是否不是 Microsoft 的意图。

如果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);
}
}

最佳答案

我猜这是故意的。 C# 遵循“成功坑”设计 - 他们希望它很难犯错。

最终,现有设计可以更轻松地分析该方法的使用。那是什么意思?嗯:

你举的例子很简单,一眼就能看出它并没有真正修改列表。但在几乎所有真实世界的代码中,情况并非如此。插入的序列几乎肯定是动态创建的,并且几乎可以是一个空序列几乎是随机的。一个空序列真的应该有不同的行为吗?如果插入的序列为空,您的代码将起作用,但是当您在其中放入真实的东西时,咔哒一声。

想象一下,如果您首先编写这段代码并且您的所有序列都是空的;看起来它有效。然后,在任意时间之后,您有一个非空插入。现在你得到了异常(exception)。您插入问题和检测问题之间的距离可能非常大。

随着它抛出任何成功的调用,故障模式变得更容易检测。

关于c# - List<T>.InsertRange() 或设计决策中的错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11026735/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com