19.2.7 List<T>

List<T>类是ArrayList类的泛型等效版本,两者功能相似。图19-17是List<T>的类图,可以看到它实现了6个接口,实际上是3对接口:

❑IEnumerable<T>和IEnumerable

❑ICollection<T>和ICollection

❑IList<T>和IList

使用泛型版本的List<T>有很多好处,比如类型安全和可以存储引用类型以及值类型的数据(ArrayList只能存储System.Object类型的数据,这将避免在ArrayList存储值类型数据时的装箱、拆箱操作,以及在存储引用类型时的显式类型转换操作,因此泛型版本的List<T>会带来一定的性能优势)。当需要创建一个List<T>类型的对象时,需要指定类型参数T的值,即指定该集合将要包含的元素的数据类型,这就保证此集合只包含该种类型的元素数据。另外,泛型版本的List<T>还提供了对集合元素的搜索、排序以及其他的方法。

19.2.7 List<T> - 图1

图 19-17 List<T>的类图

List<T>类的一些重要特性如下:

❑可以添加null值到集合中;

❑允许集合中的元素重复;

❑可以使用整数索引访问此集合中的元素,索引从零开始。

接下来,我们介绍List<T>的公共构造函数,这些公共构造函数对应着该类实例化的方式。List<T>共有3个公共构造函数:

❑List():使用默认初始容量初始化一个空的实例;

❑List(IEnumerable<T>collection):创建一个实例,并从指定集合collection中复制元素到新实例中;

❑List(int capacity):使用指定的初始容量初始化一个空的实例。

如果在创建List<T>的对象时,没有指定List<T>列表的容量大小,则默认容量大小是0。但是一旦有数据加入到列表,则列表的容量就会自动扩展到4;而当第5个元素加入时,列表容量就自动扩展到8;同理,当第9个数据加入,容量就会扩展到16……由此可见,列表容量总是成倍地增长。由此带来一个问题,扩展时要重新申请内存,这样会影响效率,如果事先知道元素数目,或者可能的数目(尽量大的估计值),建议使用一个初始容量值来实例化List<T>对象。

List<T>类的一些主要成员如表19-7所示。

19.2.7 List<T> - 图2

接下来,我们通过示例代码,学习如何声明并初始化一个List<T>类型的变量,以及它的一些成员方法和属性的用法。

首先,声明一个List<T>类型的变量,并添加4个元素,这里的T是string类型,如代码清单19-7所示。

代码清单19-7 声明List<T>类型的变量并添加元素


List<string>sampleList=new List<string>(10);

sampleList.Add("a");

sampleList.Add("b");

sampleList.Add("c");

sampleList.Add("d");


也可以在声明的时候直接进行初始化


List<string>sampleList=new List<string>(){"a","b","c","d"};


大家有没有注意到,在代码清单19-7中,我们使用的初始容量值是10,来实例化sampleList对象,但它的实际元素数却只有4个,如果不再向此集合中添加新的元素,为了不浪费空间,可以调用集合的TrimExcess方法,以最小化集合的内存开销。但如果集合列表中的元素数不少于容量的90%,也就是不少于9个的话,TrimExcess方法将不执行任何操作,因为重新分配和复制很大的List<T>数据开销可能更大。代码清单19-8中演示了TrimExcess方法的使用方法。

代码清单19-8 TrimExcess方法使用示例


using System;

using System.Collections.Generic;

namespace ProgrammingCSharp4

{

class CollectionSample

{

public static void Main()

{

List<string>sampleList=new List<string>(10);

sampleList.Add("a");

sampleList.Add("b");

sampleList.Add("c");

sampleList.Add("d");

PrintListInfo(sampleList);

sampleList.TrimExcess();

PrintListInfo(sampleList);

}

private static void PrintListInfo(List<string>sampleList)

{

Console.WriteLine("Capacity:{0}",sampleList.Capacity);

Console.WriteLine("Count:{0}",sampleList.Count);

}

}

}


上述代码的运行结果如下:


Capacity:10

Count:4

Capacity:4

Count:4

请按任意键继续……


如果我们想在第1个元素之前再插入1个新的元素,该如何做呢?可以使用Insert方法,如代码清单19-9所示。

代码清单19-9 Insert方法示例


using System;

using System.Collections.Generic;

namespace ProgrammingCSharp4

{

class CollectionSample

{

public static void Main()

{

List<string>sampleList=new List<string>(10);

sampleList.Add("a");

sampleList.Add("b");

sampleList.Add("c");

sampleList.Add("d");

sampleList.Insert(0,"e");

PrintListInfo(sampleList);

}

private static void PrintListInfo(List<string>sampleList)

{

Console.Write("sampleList:");

foreach(string str in sampleList)

{

Console.Write("\t{0}",str);

}

Console.WriteLine();

Console.WriteLine("Capacity:{0}",sampleList.Capacity);

Console.WriteLine("Count:{0}",sampleList.Count);

}

}

}


运行结果如下,可见"e"已经被插入到"a"的前面了:


sampleList:e a b c d

Capacity:10

Count:5

请按任意键继续……


❑接下来介绍下一组方法,包括:Exists、Find、FindAll、FindIndex、FindLast、FindLastIndex,它们的签名相似(有些方法具有多个重载,我们只列出其中的一个重载),使用方法也类似,因此这里我们使用Find和FindIndex方法作为代表,演示这些方法的用法。表19-8中是这些方法的签名。

19.2.7 List<T> - 图3

注意观察这些方法的签名,它们有一个共同点,都有一个共同的参数:Predicate<T>match,实际上,Predicate<T>是一个泛型委托类型,它的签名如下:


public delegate bool Predicate<T>(T obj);


从签名分析得知,该委托可以代理带有一个参数且返回值为布尔值的方法。在第15章已经学习过,匿名方法和λ表达式和委托本质上是相同的,因此,在代码清单19-10中,分别使用了“匿名方法”和“λ表达式”两种方式,演示了如何搜索List<T>列表中的元素。

代码清单19-10 搜索List<T>的数据元素代码示例


using System;

using System.Collections.Generic;

namespace ProgrammingCSharp4

{

class CollectionSample

{

public static void Main()

{

List<string>sampleList=new List<string>(10);

sampleList.Add("a");

sampleList.Add("b");

sampleList.Add("c");

sampleList.Add("d");

string result=sampleList.Find(delegate(string str)

{

return str.Equals("c");

});

Console.WriteLine(result);

int index=sampleList.FindIndex(delegate(string str)

{

return str.Equals("c");

});

Console.WriteLine(index);

result=sampleList.Find(str=>str.Equals("c"));

Console.WriteLine(result);

index=sampleList.FindIndex(str=>str.Equals("c"));

Console.WriteLine(index);

}

}

}


上述代码的运行结果为:


c

2

c

2

请按任意键继续……


最后,我们介绍下如何对列表中的元素进行排序。排序共涉及4个重载方法,它们如表19-9所示。其中第1个重载使用的是默认比较器,第2个使用指定的比较器,使用System.Comparison<T>委托,第4个使用指定的比较器对某个范围内的元素进行排序。

如果集合中的数据元素为简单类型,可以使用Sort方法,它使用的是默认比较器,此种情况比较简单,这里不做介绍。当数据元素较为复杂,换句话说,当需要根据某些自定义类型的某个或者某几个值进行排序时,默认比较器就无法满足要求了,我们可以使用基于System.Comparison<T>委托的Sort方法,如代码清单19-11所示。

19.2.7 List<T> - 图4

在看示例代码前,先来看看System.Comparison<T>委托的签名,这样可以更容易地理解代码清单19-11中Sort方法的用法。其签名如下:


public delegate int Comparison<T>(T x,T y);


示例代码如代码清单19-11所示。

代码清单19-11 使用System.Comparison<T>委托代码示例


using System;

using System.Collections.Generic;

namespace ProgrammingCSharp4

{

class CollectionSample

{

public static void Main()

{

List<SampleValue>sampleList=new List<SampleValue>(10);

sampleList.Add(new SampleValue{Value=6});

sampleList.Add(new SampleValue{Value=2});

sampleList.Add(new SampleValue{Value=1});

sampleList.Add(new SampleValue{Value=8});

Console.WriteLine(“排序前:”);

PrintListInfo(sampleList);

sampleList.Sort(delegate(SampleValue x,SampleValue y)

{

if(x==null)

{

if(y==null)

{

//如果x和y均为null,则它们相等

return 0;

}

else

{

//x比y小

return-1;

}

}

else

{

//如果x不为null

if(y==null)

//如果y为null,则x比y大

{

return 1;

}

else

{

//如果y不为null,则比较它们的Value属性值

return x.Value.CompareTo(y.Value);

}

}

});

Console.WriteLine(“排序后:”);

PrintListInfo(sampleList);

}

public class SampleValue

{

public int Value

{

get;

set;

}

}

private static void PrintListInfo(List<SampleValue>sampleList)

{

Console.Write("sampleList:");

foreach(SampleValue val in sampleList)

{

Console.Write("\t{0}",val.Value);

}

Console.WriteLine();

Console.WriteLine("Capacity:{0}",sampleList.Capacity);

Console.WriteLine("Count:{0}",sampleList.Count);

}

}

}


上述代码的运行结果如下:


排序前:

sampleList:6 2 1 8

Capacity:10

Count:4

排序后:

sampleList:1 2 6 8

Capacity:10

Count:4

请按任意键继续……


也可以使用自定义的比较器达到排序的目的,自定义比较器类型需要实现IComparer<T>接口。代码清单19-12是一个自定义比较器的示例。

代码清单19-12 使用自定义比较器代码示例


using System;

using System.Collections.Generic;

namespace ProgrammingCSharp4

{

class CollectionSample

{

public static void Main()

{

List<SampleValue>sampleList=new List<SampleValue>(10);

sampleList.Add(new SampleValue{Value=6});

sampleList.Add(new SampleValue{Value=2});

sampleList.Add(new SampleValue{Value=1});

sampleList.Add(new SampleValue{Value=8});

Console.WriteLine(“排序前:”);

PrintListInfo(sampleList);

sampleList.Sort(new SampleValueComparer());

Console.WriteLine(“排序后:”);

PrintListInfo(sampleList);

}

public class SampleValue

{

public int Value

{

get;

set;

}

}

public class SampleValueComparer:IComparer<SampleValue>

{

public int Compare(SampleValue x,SampleValue y)

{

if(x==null)

{

if(y==null)

{

//如果x和y均为null,则它们相等

return 0;

}

else

{

//x比y小

return-1;

}

}

else

{

//如果x不为null

if(y==null)

//如果y为null,则x比y大

{

return 1;

}

else

{

//如果y不为null,则比较它们的Value属性值

return x.Value.CompareTo(y.Value);

}

}

}

}

private static void PrintListInfo(List<SampleValue>sampleList)

{

Console.Write("sampleList:");

foreach(SampleValue val in sampleList)

{

Console.Write("\t{0}",val.Value);

}

Console.WriteLine();

Console.WriteLine("Capacity:{0}",sampleList.Capacity);

Console.WriteLine("Count:{0}",sampleList.Count);

}

}

}


上述代码的运行结果如下:


排序前:

sampleList:6 2 1 8

Capacity:10

Count:4

排序后:

sampleList:1 2 6 8

Capacity:10

Count:4

请按任意键继续……