在上一篇随笔<.NET Framework源码研究系列之---Delegate>中我们一起研究了.NET中是如何实现委托的.今天我们一起研究一下.NET中我们用的最多的一个集合类之一List.

  大家都知道,在.NET集合类中List如Array一样都是一个顺序一维数组,与Array不同的是,我们可以更方便的操作List类型的集合,比如插入数据,删除数据,排序等等,那么.NET源码中List是如何实现的呢?我们在使用List相对Array的优点时会不会有其他方面的代价呢?从List的源码中我们又能学到什么呢?带着这些问题,让我们开始今天的.NET源码研究之旅吧.

  研究一开始,首先我们看一下List的类型定义.通过对象浏览器,我们很容易知道List继承了IList,IEnumerator,ICollection等接口..NET中是这么实现的:

  public class List<T> : IList<T>, System.Collections.IList

  这里与对象浏览器里看到的不一样,是因为IList接口同样继承自IEnumerator,ICollection接口.

  

  接下来我们看List包含的字段和构造函数:

代码
public class List<T> : IList<T>, System.Collections.IList{
private const int _defaultCapacity = 4;
private T[] _items;
private int _size;
private int _version;
[NonSerialized]
private Object _syncRoot;
static T[] _emptyArray = new T[0];
public List(){
_items
= _emptyArray;
}
public List(int capacity){
if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
_items
= new T[capacity];
}
public List(IEnumerable<T> collection){
if (collection == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);

ICollection
<T> c = collection as ICollection<T>;
if (c != null){
int count = c.Count;
_items
= new T[count];
c.CopyTo(_items,
0);
_size
= count;
}
else{
_size
= 0;
_items
= new T[_defaultCapacity];

using (IEnumerator<T> en = collection.GetEnumerator()){
while (en.MoveNext()){
Add(en.Current);
}
}
}
}
请注意List的第三个构造函数.从以上代码我们可以发现原来List内部是用了一个Array来存储数据的.有此我们可以认为List不过是对Array的一层包装,也有此可以断定List的性能肯定不如Array(包装往往会带来性能的损失).

  然后我们看下List的几个字段.由private const int _defaultCapacity = 4;我们可以看出List默认最少可以包含4个元素._size字段顾名思义是List当前的长度.通过访问List.Capacity属性我们可以获取List当前可以包含多少个元素.通过访问List.Count属性可以获得List当前包含的元素数.那么他们有什么不同呢?经过仔细研究发现,原来List自增长的实现是这样子的.每当添加元素的时候,List会先判断_size与_items的长度,如果相等,则size扩展到_size+1到_items.Length*2.详情请看如下源码:

代码
public void Add(T item){
if (_size == _items.Length) EnsureCapacity(_size + 1);
_items[_size
++] = item;
_version
++;
}
private void EnsureCapacity(int min){
if (_items.Length < min){
int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;
if (newCapacity < min) newCapacity = min;
Capacity
= newCapacity;
}
}
public int Capacity{
get { return _items.Length; }
set{
if (value != _items.Length){
if (value < _size){
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value > 0){
T[] newItems
= new T[value];
if (_size > 0){
Array.Copy(_items,
0, newItems, 0, _size);
}
_items
= newItems;
}
else{
_items
= _emptyArray;
}
}
}
}
 通过以上代码可以获知List自增长的原理.也有此可知,最坏的情况下,我们插入一个元素可能导致把前面所有的元素复制到新的数组中去,由此产生的性能问题可见一斑.

  仔细看上面的代码,我们会发现当复制数组时List并没有自己做,而是调用了Array.Copy这个方法.遍历整个List的实现代码,我们发现List几乎所有的数据操作,如插入,删除,查询,排序,检索等等都是通过Array中相应的静态方法实现的.由此可知上面我说的"List是对Array的一层包装"是正确的.

  我们知道List继承了ICollection接口,看对它的实现,我们发现ICollection.IsSynchronized属性直接返回了false.由此可知List不是线程安全的(用多线程的同学要注意咯,这点与Queue不同).

  在.NET中,遍历一个数组我们可以用两种方法,一个for循环,二是foreach.这两个的区别是一个是顺序遍历,另外一个跟顺序没什么关系..NET中凡是Array和List都支持这两种遍历.学习过设计模式中迭代模式的人这时候看到"与顺序无关的遍历"是不是觉得很熟啊.设计模式中对迭代模式的定义为:

提供一种方法遍历访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。

 

没错!List就是一个.NET自带的一个迭代器.严格来说,是因为List实现了IEnumerator接口.也就是说.NET中凡是实现了IEnumerator接口的都是迭代器.大家在工作中无意识的就拥到的设计模式,是不是觉得很开心呢?!:)

  代码看到最后,发现List也提供了Reverse()方法用于倒排存储的所有元素,该方法的实现居然完全照搬Array.Reverse,至此不得不感慨微软对代码的复用简直做到了极致.也发现了以往居然把这么强大的东西(Array)丢掉了.

  最后我们看两个List都有的方法(没有抄袭Array,不容易啊):

 

代码
public void TrimExcess(){
int threshold = (int)(((double)_items.Length) * 0.9);
if (_size < threshold){
Capacity
= _size;
}
}
public bool TrueForAll(Predicate<T> match){
if (match == null){
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < _size; i++){
if (!match(_items[i])){
return false;
}
}
return true;
}

  这两个方法里TrueForAll则是判断List中所有的元素是否满足制定的条件;TrimExcess的功能是去掉List自增长时增加的多余空间,效果看下面的示例代码.

 

 

代码
List<int> test1 = new List<int>(new int[]{0,1,2,4});
Console.WriteLine(
"集合实际长度为:{0}",test1.Capacity);
test1.Add(
5) ;
Console.WriteLine(
"集合实际长度为:{0}", test1.Capacity);
test1.TrimExcess();
Console.WriteLine(
"集合实际长度为:{0}", test1.Capacity);

运行效果为

 

 

小结:

  今天我们一起研究了.NET Framework是如何实现List,发现了使用List可能带来的问题,List自身的一些特点,想必参看教程,大家更能深入理解List. 

posted on 2010-07-24 22:01  倪大虾  阅读(3792)  评论(16编辑  收藏  举报