在.NET编程中,经常需要在List
List
private T[] _items;
private int _size;
private object _syncRoot;
// 不调用SyncRoot则不使用
private int _version;
因此,List
List
public T this[int index]
{
get
{
if((uint)index >= (uint)_size)
ThrowHelper.ThrowArgumentOutOfRangeException();
return _items[index];
}
set
{
if((uint)index >= (uint)_size)
ThrowHelper.ThrowArgumentOutOfRangeException();
_items[index] = value;
_version++;
}
}
注意,_items[index]操作隐含了一个范围检查:CLR在实际读取或写入数组之前执行了一个等效于(uint)index < (uint)_items.Length的检查。因此,这里实际上有两个范围检查。JIT通常不知道_size < _items.Length,因此它不能基于第一个检查的结果来消除第二个检查。
如果使用普通的数组,不能简单地“添加”或“删除”元素,因为数组有一个固定的大小。因此,如果决定使用普通数组,但需要添加、删除或(天哪!)插入元素,最终可能会重新实现List
private T[] _array;
private int _count;
然后可能会编写一堆代码来执行List
static T[] Insert(int index, T item, T[] array, ref int count)
{
Debug.Assert((uint)index <= (uint)count);
if(count == array.Length)
{
int newCap = array.Length * 2;
array = CopyToNewArray(array, count, newCap);
}
for(int i = count; i > index; i--)
array[i] = array[i - 1];
array[index] = item;
count++;
return array;
}
当然,这条路通向疯狂。幸运的是,永远不需要编写这样的代码:只需使用InternalList
InternalList
[Serializable]
public struct InternalList : IListAndListSource, IListRangeMethods, ICloneable>
{
public static readonly T[] EmptyArray = new T[0];
public static readonly InternalList Empty = new InternalList(0);
private T[] _array;
private int _count;
public InternalList(int capacity) {...}
public InternalList(T[] array, int count) { _array=array; _count=count; }
public InternalList(IEnumerable items) : this(items.GetEnumerator()) {}
public InternalList(IEnumerator items) {}
public int Count {...}
public int Capacity {...}
public void Resize(int newSize, bool allowReduceCapacity = true) {...}
public void Add(T item) {...}
...
}
为了消除List
InternalList还有一个InternalArray属性,它提供了对内部数组的访问。这实际上允许绕过普通List
List pts;
...
// 错误 CS1612: 不能修改 'List.this[int]' 的返回值,因为它不是一个变量
pts[0].X = 5;
但如果pts是一个InternalList,那么可以编写:
pts.InternalArray[0].X = 5;
InternalList
但应该理解的是,InternalList
再次强调,当通过值传递InternalList时,_count和_array变量的副本被创建。对这些变量的更改不会影响其他副本,但对_array元素的更改会影响其他副本(顺便说一句,这与D中切片的变异行为类似)。如果想从公共API返回一个内部列表,可以将其转换为IList
最后,除了InternalList