抛除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源码开头部分如下:
我们再来看看List在进行下标取值的代码:
可见在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;
}
}
项目在写一个接口的时候,需要根据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的值如果能设置的合理,那么可以减少内存申请、拷贝的次数,效率也会提高很多。
相关文章推荐
- 抛除C++旧印象(二):C#Dictionary源码剖析
- c# 源码深度剖析 list
- c++ poco Mutex相关 源码剖析
- STL源码剖析学习五:list
- Arrays.asList()源码剖析 (转)
- c++ poco StreamSocket 源码剖析
- 将C#程序与调用的dll对应的C++源码联调的一种方法
- STL 源码剖析读书笔记三:序列式容器之 vector、list
- C++和C#进程间通过命名管道来通信(附源码)—上
- c/c++:strlen源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- C# ArrayList源码剖析
- C++和C#进程之间通过命名管道通信(附源码)—下
- Redis源码剖析和注释(十)--- 列表键命令实现(t_list)
- 【Java集合源码剖析】LinkedList源码剖析
- LinkedList源码剖析
- C# Dictionary源码剖析
- Working on LinkedList Using C#/C/C++
- C++和C#进程间通过命名管道来通信(附源码)—上