您的位置:首页 > 编程语言 > C语言/C++

抛除C++旧印象(一):C#List源码剖析

2017-07-30 11:54 351 查看
    项目中的好多同学都是之前写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的值如果能设置的合理,那么可以减少内存申请、拷贝的次数,效率也会提高很多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息