首先我们来看看List<T>这个泛型类
根据下面代码总结:当我们在创建一个List<T>类型对象的时候,如果没有给List<T>指定一个初始长度,那么也就是说,当我们执行0参数的构造方法时,微软为我们创建了一个T类型的个数为0的数组
当我们调用Add方法时候,List会新建一个数组,然后把数组的长度设置为原来的二倍(如果原有的数组长度为0,那就默认将数组的长度设置为4,如果List的容量已经达到此时List容量的最大值,那么就会新构造了一个数组,数组的长度设置为原来的二倍,然后把元素拷贝过去,旧的数组将会给GC回收)
根据以下代码:我想证明的是:其实List<T>不过是对Array的进一步封装,说得再直接点,我愿意理解List<T>为Array的可扩充版本,然后扩展了一些方法;
那关于Array和List的选择,我重新做一个说明:
List是基于Array存在的,因此,在创建一个List对象时,需要耗费比Array相对更多的时间,以及更大的空间,因为List除了初始化内部的items外还需要初始化一些其他的属性。而且在方法调用时,这点我没有证实,只是一个猜测,List需要的是再去调用Array的相关方法,因此也许会存在方法调用的时间消耗问题。
因此,我的建议是:
如果初始化时确定大小,那么就使用Array。
如果初始化时不确定大小,那么就使用List。当然,其实完全可以自己去实现List中的数组扩充功能的,也许会更棒,因为我们没有必要去将Array每次都扩充为原来的二倍。
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{
private static readonly T[] _emptyArray = new T[0];
private const int _defaultCapacity = 4; //表示默认的初始容量,即内部容器数组的Length
private T[] _items; //这个真正存储数据的内部数组
private int _size; //表示List中存储元素的个数
private int _version;//表示一个版本,当Add元素或者Remove元素等时候,会自增。我们在foreach list过程中如果list改变了,那么会抛出异常(好像是集合已修改,不能枚举),就是根据它来判断的。
private List<T> _list;
private object _root; //在调用Add方法等方法的时候,坐look锁用的
public List() //List<T>泛型类的无参的构造方法
{
//当无参的时候,.NET Framework其实是用一个_emptyArray来初始化了List中的items集合。那么_emptyArray又是什么呢?
//_emptyArray其实是一个T类型的数组,个数为0。那么也就是说,当我们执行0参数的构造方法时,系统是把items集合给赋值为了一个T类型的个数为0的数组
this._items = List<T>._emptyArray;
}
public void Add(T item)
{
if (this._size == this._items.Length) //在新增的时候,判断一下_items中存储元素的个数是否等于_items数组的实际长度
{
this.EnsureCapacity(this._size + 1); //当空间满时,调整空间
}
T[] arg_36_0 = this._items;
int size = this._size;
this._size = size + 1;
arg_36_0[size] = item;
this._version++;
}
private void EnsureCapacity(int min)
{
if (this._items.Length < min)
{
int num = (this._items.Length == 0) ? 4 : (this._items.Length * 2); //新的数据的默认大小为4,或者在原来的的长度基础上长度加倍
if (num > 2146435071)
{
num = 2146435071;
}
if (num < min)
{
num = min;
}
this.Capacity = num;
}
}
public int Capacity
{
[__DynamicallyInvokable]
get
{
return this._items.Length;
}
[__DynamicallyInvokable]
set
{
if (value < this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value != this._items.Length)
{
if (value > 0)
{
T[] array = new T[value];
if (this._size > 0)
{
Array.Copy(this._items, 0, array, 0, this._size);
}
this._items = array;
return;
}
this._items = List<T>._emptyArray;
}
}
}
}
泛型类是使用,及概念
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace 泛型
{
//定义一个泛型类,该类有两个类型参数,分别是T,S
public class Test<T,N>
{
private T Name { get; set; }
private N Age { get; set; }
//构造函数,初始化两个属性
public Test(T name,N age)
{
this.Name = name;
this.Age = age;
}
public void SetValue()
{
Console.WriteLine("Hi,我的名字叫{0},我今年{1}岁", Name, Age);
}
}
class Program
{
static void Main(string[] args)
{
//使用string,int来实例化Test<T,N>类。这里调用了带两个参数的构造函数
Test<string, int> t = new Test<string, int>("tom", 25);
t.SetValue();
Console.ReadKey();
}
}
}
继承泛型类
上面的例子不是很恰当,目的是让初学泛型的你了解一下泛型的定义及实例化方法,如上,我们定义了一个泛型类,那么如何实现C#泛型类的继承呢?这里需要满足下面两点中的任何一点即可:
1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;
2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;
//如果这样写泛型继承的话,显然会报找不到类型T,S的错误。
//public class TestChild : Test<T, S> { }
//正确写泛型类继承的写法应该是这样的
public class TestChild : Test<string, int> { }
public class TestChild<T, S> : Test<T, S> { }
public class TestChild<T, S> : Test<string, int> { }
继承泛型接口
接着我们来看看泛型接口,其创建以及继承规则和上面说的泛型类是一样的,看下面的代码:
接口中只能包含方法、属性、索引器和事件的声明。不允许声明成员上的修饰符,即使是pubilc都不行,因为接口成员总是公有的,也不能声明为虚拟和静态的。如果需要修饰符,最好让实现类来声明。
接口默认访问符是internal
接口的成员默认访问修饰符是public
public interface IList<T>
{
T[] GetElements(); //定义了一个GetElements()方法,这个方法的返回值类型为T类型的数组。相当于 pulbic string[] GetElements()
}
public interface IDictionary<K, V> //定义一个泛型接口,该接口有两个类型参数,分别是K,V ;如果是普通的数组就这样声明public interface IDictionary
{
void Add(K key, V value);
}
//定义一个List<T>泛型类,让它继承IList<T>,IDictionary<int ,T>泛型接口
//泛型接口的类型参数要么已经实例化
//要么来源于实现类声明的类型化参数
class List<T> : IList<T>, IDictionary<int, T>
{
public T[] GetElements() //实现泛型接口
{
return null;
}
public void Add(int index, T value) //实现泛型接口
{
Console.WriteLine ("第{0}项的值是{1}",index,value);
}
}