.NET Framework为动态列表提供了泛型类List<T>。这个类实现了IList、ICollection、IEnumerable、IList<T>、ICollection<T>和IEnumerable<T>接口。
下面的例子将Racer类中的成员用作要添加到集合中的元素,以表示一级方程式的一位赛车手。这个类有5个属性:Id、FirstName、LastName、Country和Wins的次数。在该类的构造函数中,可以传递赛车手的姓名和获胜次数,以设置成员。重写ToString()方法是为了返回赛车手的姓名。Racer类也实现了泛型接口IComparable<t>,为了Racer类中的元素排序,还实现了IFormattable接口:
using System;
using System.Collections.Generic;
namespace 列表
{
class Program
{
static void Main(string[] args)
{
// List<Racer> racers = new List<Racer> {
// new Racer(3,"gwewf","fwefwf","India",98),
// new Racer(8,"fgohhwe","fkjoe","China",12),
// new Racer(7,"inuneg","jowehwe","America",23),
// new Racer(5,"fwfwf","ygdvs","koera",98),
// new Racer(11,"jlffwfef","zfefewof","Japan",12)
// };
// racers.Sort();
// foreach(var racer in racers){
// System.Console.WriteLine($"{racer:L}");
// }
}
}
public class Racer: IComparable<Racer>,IFormattable{
public int Id{get;}
public string FirstName{get;}
public string LastName{get;}
public string Country{get;}
public int Wins{get;}
public Racer(int id,string firstName,string lastName,string country,int wins){
Id = id;
FirstName = firstName;
LastName = lastName;
Country = country;
Wins = wins;
}
public Racer(int id,string firstName,string lastName,string country)
:this(id,firstName,lastName,country,wins:0)
{
}
public override string ToString()=>$"{FirstName} {LastName}";
public string ToString(string format,IFormatProvider formatProvider){
if(format == null) format = "N";
switch(format){
case "N": return ToString();
case "F": return FirstName;
case "L": return LastName;
case "W": return $"{ToString()}, Wins: {Wins}";
case "C": return $"{ToString()}, Country: {Country}";
case "A": return $"{ToString()}, Country: {Country}, Wins: {Wins}";
default:
throw new FormatException(string.Format(formatProvider,$"Format{format} is not supported"));
}
}
public string ToString(string format)=>ToString(format,null);
public int CompareTo(Racer other){
int compare = LastName?.CompareTo(other?.LastName)??-1;
if(compare == 0){
return FirstName?.CompareTo(other?.FirstName)??-1;
}
return compare;
}
}
}
创建列表
调用默认的构造函数,就可以创建列表对象。在泛型类List<T>中,必须为声明为列表的值指定类型。下面的代码说明了如何声明一个包含int的List<T>泛型类和一个包含Racer元素的列表。ArrayList是一个非泛型列表,它可以将任意Object类型作为其元素。
使用默认的构造函数创建一个空列表。元素添加到列表中后,列表的容量就会扩大为可接纳4个元素。如果添加了第5个元素,列表的大小就重新设置为包含8个元素。如果8个元素还不够,列表的大小就重新设置为包含16个元素。每次都会将列表的容量重新设置为原来的2倍。
var intList = new List<int>();
var racers = new List<Racer>();
如果列表的容量改变了,整个集合就要重新分配到一个新的内存块中。在List<T>泛型类的实现代码中,使用了一个T类型的数组。通过重新分配内存,创建一个新数组,Array.Copy()方法将旧数组中的元素复制到新数组中。为节省时间,如果事先知道列表中元素的个数,就可以用构造函数定义其容量。下面创建了一个容量为10个元素的集合。如果该容量不足以容纳要添加的元素,就把集合的大小重新设置为包含20或40个元素,每次都是原来的2倍。
List<int> intList1 = new List<int>(10);
//使用Capacity属性可以获取和设置集合的容量。
intList1.Capacity = 20;
容量和集合中元素的个数不同。集合中的元素个数可以用Count属性读取。当然,容量总是大于或等于元素个数。只要不把元素添加到列表中,元素个数就是0。
System.Console.WriteLine(intList1.Count);
如果已经将元素添加到列表中,且不希望添加更多的元素,就可以调用TrimExcess()方法,去除不需要的容量。但是,因为重新定位需要时间,所以如果元素个数超过了容量的90%,TrimExcess()方法就什么也不做。
intList1.TrimExcess();
1. 集合初始值设定项
还可以使用集合初始值设定项给集合赋值。集合初始化器的语法类似于数组初始化器。使用集合初始值设定项,可以在初始化集合时,在花括号中给集合赋值:
var intList2 = new List<int>() {1,2};
var stringList = new List<string>() {"one","two"};
//注:集合初始值设定项没有反映在已编译的程序集的IL代码中。
//编译器会把集合初始值设定项转换成对初始值设定项列表中的每一项调用Add()方法。
2. 添加元素
使用Add()方法可以给列表添加元素,如下所示。实例化的泛型类型定义了Add()方法的参数类型:
var intList3 = new List<int>();
intList3.Add(1);
intList3.Add(2);
var stringList2 = new List<string>();
stringList2.Add("one");
stringList2.Add("two");
把racers变量定义为List<Racers>类型。使用new运算符创建相同类型的一个新对象。因为类List<T>用具体类Racer来实例化,所以现在只有Racer对象可以用Add()方法添加。在下面的示例代码中,创建了5个一级方程式赛车手,并把它们添加到集合中。前3个用集合初始值设定项添加,后两个通过显示调用Add()方法来添加:
var graham = new Racer(7,"Graham","Hill","UK",14);
var emerson = new Racer(13,"Emerson","Fittipaldi","Brazil",14);
var mario = new Racer(16,"Mario","Andretti","USA",12);
var racers = new List<Racer> {graham,emerson,mario};
racers.Add(new Racer(24,"Michael","Schumacher","Germany",91));
racers.Add(new Racer(27,"Mika","Hakkinen","Finland",20));
//使用List<T>类的AddRange()方法,可以一次给集合添加多个元素。因为AddRange()方法的参数是
//IEnumerable<T>类型的对象,所以也可以传递一个数组,如下所示。
racers.AddRange(
new Racer[] {
new Racer(14,"Niki","Lauda","Austria",25),
new Racer(21,"Alain","Prost","France",51)
}
);
//注:集合初始值设定项只能在声明集合时使用。AddRange()方法则可以在初始化集合后调用。
//如果在创建集合后动态获取数据,就需要调用AddRange()。
如果在实例化列表时知道集合的元素个数,就也可以将实现IEnumerable<T>类型的任意对象传递给类的构造函数。这非常类似于AddRange()方法:
var racers = new List<Racer>(
new Racer[] {
new Racer(12,"Jochen","Rindt","Austria",6),
new Racer(22,"Ayrton","enna","Brazil",41)
}
);
3. 插入元素
使用Insert()方法可以在指定位置插入元素:
racers.Insert(3,new Racer(6,"Phil","Hill","USA",3));
方法InsertRange()提供了插入大量元素的功能,类似于前面的AddRange()方法。
如果索引集大于集合中的元素个数,就抛出ArgumentOutOfRangeException类型的异常。
4. 访问元素
实现了IList和IList<T>接口的所有类都提供了一个索引器,所以可以使用索引器,通过传递元素号来访问元素。第一个元素可以用索引值0来访问。指定racers[3],可以访问列表中的第4个元素:
Racer r1 = racers[3];
可以使用Count属性确定元素个数,再使用for循环遍历集合中的每个元素,并使用索引器访问每一项:
for(int i = 0;i < racers.Count; i++){
System.Console.WriteLine(racers[i]);
}
注:
可以通过索引访问的集合类有ArrayList、StringCollection和List<T>。
因为List<T>集合类实现了IEnumerable接口,所以也可以使用过foreach语句遍历集合中的元素:
foreach(var r in racers){
System.Console.WriteLine(r);
}
注:
编译器解析foreach语句时,利用了IEnumerable和IEnumerator接口。
5. 删除元素
删除元素时,可以利用索引,也可以传递要删除的元素。下面的代码把3传递给RemoveAt()方法,删除第4个元素:
racers.RemoveAt(3);
也可以直接将Racer对象传递给Remove()方法,来删除这个元素。按索引删除比较快,因为必须在集合中搜索要删除的元素。Remove()方法先在集合中搜索,用IndexOf()方法获取元素的索引,再使用该索引删除元素。IndexOf()方法先检查元素类型是否实现了IEquatable<T>接口。如果是就调用这个接口的Equals()方法,确定集合中的元素是否等于传递给Equals()方法的元素。如果没有实现这个接口,就使用Object类的Equals()方法比较这些元素。Object类中Equals()方法的默认实现代码对值类型进行按位比较,对引用类型只比较其引用。
这里从集合中删除了变量graham引用的赛车手。变量graham是前面在填充集合时创建的。因为IEquatable<T>接口和Object.Equals()方法都没有在Racer类中重写,所以不能用要删除元素的相同内容创建一个新对象,再把它传递给Remove()方法:
if(!racers.Remove(graham)){
System.Console.WriteLine("Object not found in collection");
}
RemoveRange()方法可以从集合中删除许多元素。它的第一个参数指定了开始删除的元素索引,第二个参数指定了要删除元素的个数:
int index = 3;
int count = 3;
racers.RemoveRange(index,count);
要从集合中删除有指定特性的所有元素,可以使用RemoveAll()方法。这个方法在搜索元素时使用下面将讨论的Predicate<T>参数。要删除集合中的所有元素,可以使用ICollection<T>接口定义的Clear()方法。
6. 搜索
有不同的方式在集合中搜索元素。可以获得要查找的元素的索引,或这搜索元素本身。可以使用的方法有IndexOf()、LastIndexOf()、FindIndex()、FindLastIndex()、Find()和FindLast()。如果只检查元素是否存在,List<T>类就提供了Exists()方法。
IndexOf()方法需要将一个对象作为参数,如果在集合中找到该元素,这个方法就返回该元素的索引。如果没有找到该元素,就返回-1。IndexOf()方法使用IEquatable<T>接口来比较元素:
int index = racers.IndexOf(graham);
使用IndexOf()方法,还可以指定不需要搜索整个集合,但必须指定从哪个索引开始搜索以及比较时要迭代的元素个数。
除了使用IndexOf()方法搜索指定的元素之外,还可以搜索有某个特征的元素,该特性可以用FindIndex()方法来定义。FindIndex()方法需要一个Predicate类型的参数:
public int FindIndex(Predicate<T> match);
Predicate<T>类型是一个委托,该委托返回一个布尔值,并且需要把类型T作为参数。如果Predicate<T>委托返回true,就表示有一个匹配元素,并且找到了相应的元素。如果它返回false,就表示没有找到元素,搜索将继续。
public delegate bool Predicate<T>(T obj);
在List<T>类中,把Racer对象作为类型T,所以可以将一个方法(该方法将类型Racer定义为一个参数且返回一个布尔值)的地址传递给FindIndex()方法。查找指定国家的第一个赛车手时,可以创建如下所示的FindCountry类。FindCountryPredicate()方法的签名和返回类型通过Predicate<T>委托定义。Find()方法使用变量country搜索用FindCountry类的构造函数定义的某个国家:
public class FindCountry{
private string _country;
public FindCountry(string country)=>_country = country;
public bool FindCountryPredicate(Racer racer)=>racer?.Country == _country;
}
使用FindIndex()方法可以创建FindCountry类的一个新实例,把表示一个国家的字符串传递给构造函数,再传递Find()方法的地址。再下面的示例中,FindIndex()方法成功完成后,iindex2就包含集合中赛车手的Country属性设置为Japan的第一项的索引:
int index2 = racers.FindIndex(new FindCountry("Japan").FindCountryPredicate);
除了用处理程序方法创建类之外,还可以在这里创建lambda表达式。结果与前面完全相同。现在lambda表达式定义了实现代码,来搜索Country属性设置为Japan的元素:
int index = racers.FindIndex(r=>r.Country == "Japan");
与IndexOf()方法类似,使用FindIndex()方法也可以指定搜索开始的索引和要遍历的元素个数。为了从集合中的最后一个元素开始向前搜索某个索引,可以使用FindLastIndex()方法。
FindIndex()方法返回所查找元素的索引。除了获得索引之外,还可以直接获得集合中的元素。Find()方法需要一个Predicate<T>类型的参数,这与FindIndex()方法类似。下面的Find()方法搜索列表中FirstName属性设置为Niki的第一个赛车手。当然,也可以实现FindLast()方法,查找与Predicate<T>类型匹配的最后一项。
Racer racer= racers.Find(r=>r.FirstName == "Graham");
要获得与Predicate<T>类型匹配的所有项,而不是一项,可以使用FindAll()方法。FindAll()方法使用的Predicate<T>委托与Find()和FindIndex()方法相同。FindAll()方法在找到第一项后,不会停止搜索,而是继续迭代集合中的每一项,并返回Predicate<T>类型是ture的所有项。
这里调用了FindAll()方法,返回Wins属性设置为大于20的整数的所有racer项。从bigWinners列表中引用所有赢得超过20场比赛的赛车手:
List<Racer> bigWinners = racers.FindAll(r=>r.Wins > 5);
foreach(var racer in bigWinners){
System.Console.WriteLine(racer);
}