项目中的好多同学都是之前写C++的,用了unity之后才开始写C#代码,虽然说转过来很轻松,但是往往会把C++的惯性思维带过来,不自觉的就认为C#跟C++一样。
项目在写一个接口的时候,需要根据index返回list中的元素,因为原先这个接口有效率问题,所以我们在改的时候也比较谨慎,有同学提出,如果用List也是不高效的,因为在index大的时候,需要从到到位进行链表查找,效率必定不尽如人意。殊不知用链表的是C++里面的List,而C#中的List是用数组来实现的,进行下标取值效率问题根本用不着担心!
C#的List源码开头部分如下:
public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
{
private const int _defaultCapacity = 4;
private T[] _items;
[ContractPublicPropertyName("Count")]
private int _size;
private int _version;
[NonSerialized]
private Object _syncRoot;
static readonly T[] _emptyArray = new T[0];
items就是List定义的泛型数组,size是数组的大小,_version是当前List值得版本号,每次对数组的增删改操作都会使其对_version++,其主要作用是在对List进行Foreach时,判断是否有进行修改,如果版本号对不上,C#会抛除
InvalidOperationException的异常。这也就是我们在foreach用不能对相关变量进行更改的原因。
我们再来看看List在进行下标取值的代码:
public T this[int index] {
get {
// Following trick can reduce the range check by one
if ((uint) index >= (uint)_size) {
ThrowHelper.ThrowArgumentOutOfRangeException();
}
Contract.EndContractBlock();
return _items[index];
}
set {
if ((uint) index >= (uint)_size) {
ThrowHelper.ThrowArgumentOutOfRangeException();
}
Contract.EndContractBlock();
_items[index] = value;
_version++;
}
}
可见在Get的时候,只是判断了下标是否越界,如果越界则抛出
ArgumentOutOfRangeException,否则返回数组的第index个元素,Set的时候同样进行的越界判断,如果没越界,则进行赋值并版本号+1。
还有个比较重要的是Add函数和capacity值:
public void Add(T item) {
if (_size == _items.Length) EnsureCapacity(_size + 1);
_items[_size++] = item;
_version++;
}
在进行Add的时候,如果_size与item的长度相等,说明数组已经达到容纳的最大值,这时候需要进行扩容,调用EnsureCapacity函数:
private void EnsureCapacity(int min) {
if (_items.Length < min) {
int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}
public int Capacity {
get {
Contract.Ensures(Contract.Result<int>() >= 0);
return _items.Length;
}
set {
if (value < _size) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
Contract.EndContractBlock();
if (value != _items.Length) {
if (value > 0) {
T[] newItems = new T[value];
if (_size > 0) {
Array.Copy(_items, 0, newItems, 0, _size);
}
_items = newItems;
}
else {
_items = _emptyArray;
}
}
}
}
扩容的方式是把数组扩充到原来的两倍,再调用Array.Copy把原有数组的值复制到新数组中。capacity的默认值为4,在List的构造函数中可以直接对其进行设置,所以capacity的值如果能设置的合理,那么可以减少内存申请、拷贝的次数,效率也会提高很多。