List<T> 类与 ArrayList 类比较类似。它实现了 IList<T> 泛型接口,长度可以动态增加。
可以使用 Add 或 AddRange 方法将项添加到 List<T> 。
List<T> 类使用的相等比较器和排序比较器如下:
-
Contains、IndexOf、LastIndexOf、和 Remove 用于对列表中的元素进行相等比较。默认的相等比较器 T 按如下方式确定:如果类型 T 实现 IEquatable<T> 泛型接口,则相等比较器是 Equals(T) 方法该接口; 否则,默认的相等比较器是 Object.Equals(Object)。
-
BinarySearch 和 Sort 用于对列表元素进行排序。类型的默认比较器 T 按如下方式确定:如果类型 T 实现 IComparable<T> 泛型接口,则默认比较器是 CompareTo(T) 方法该接口; 否则为如果类型 T 实现非泛型 IComparable 接口,则默认比较器是 CompareTo(Object) 该接口的方法。 如果类型 T 实现两个接口,则没有默认比较器,并且必须显式提供一个比较器或比较委托。
List<T> 不保证进行排序。 您必须对 List<T> 执行的操作之前进行排序 (如 BinarySearch) 。
可以使用整数索引访问此集合中的元素。 在此集合中的索引是从零开始。
当元素个数非常多时,您可以在64位系统上,把配置信息中的 enabled 属性设置为 true ,来获取20亿的容量。
List<T> 接受值为 null 的引用类型,并允许重复。
性能注意事项
在决定是否使用 List<T> 或 ArrayList 类,这两个具有类似的功能,请记住, List<T> 类在大多数情况下更好地执行,并且是类型安全。 如果引用类型用于类型 T 的 List<T> 类,这两个类的行为是完全相同。 但是,如果值类型用于类型 T, ,您需要考虑实现和装箱问题。
如果值类型用于类型 T, ,编译器将生成的实现 List<T> 专门针对该值类型的类。 这意味着一个列表元素的 List<T> 对象没有进行装箱就可以使用该元素,并且会创建大约 500 个列表元素后不对列表元素装箱所节省的内存大于用来生成的类实现的内存。
请确保用于类型的值类型 T 实现 IEquatable<T> 泛型接口。 如果不是,方法如 Contains 必须调用 Object.Equals(Object) 框受影响的列表元素的方法。 如果值类型实现 IComparable 接口,并且您拥有源代码,则还应实现 IComparable<T> 泛型接口,以防止 BinarySearch 和 Sort 中装箱列表元素的方法。 如果您不拥有的源代码,将传递 IComparer<T> 对象传递给 BinarySearch 和 Sort 方法
若要使用强类型集合或使用特定类型的集合,则优先选择 List<T> 类,而不要选择 ArrayList 类,也不要自己写一个强类型的集合容器。是或自己编写强类型包装器集合。原因是一些功能 .NET 框架已经帮你实现,并且公共语言运行时可以共享 Microsoft 中间语言代码和元数据,而这点您的代码无法做到。
using System; using System.Collections.Generic; // 简单的业务对象,会员Id用来区分不同的会员 // 会员名称可以修改 public class Part : IEquatable<Part> { public string PartName { get; set; } public int PartId { get; set; } public override string ToString() { return "ID: " + PartId + " Name: " + PartName; } public override bool Equals(object obj) { if (obj == null) return false; Part objAsPart = obj as Part; if (objAsPart == null) return false; else return Equals(objAsPart); } public override int GetHashCode() { return PartId; } public bool Equals(Part other) { if (other == null) return false; return (this.PartId.Equals(other.PartId)); } // 还需要重载 == 合 != 运算符 // TODO.. } public class Example { public static void Main() { // 创建一个会员列表 List<Part> parts = new List<Part>(); // 向列表中添加会员 parts.Add(new Part() { PartName = "crank arm", PartId = 1234 }); parts.Add(new Part() { PartName = "chain ring", PartId = 1334 }); parts.Add(new Part() { PartName = "regular seat", PartId = 1434 }); parts.Add(new Part() { PartName = "banana seat", PartId = 1444 }); parts.Add(new Part() { PartName = "cassette", PartId = 1534 }); parts.Add(new Part() { PartName = "shift lever", PartId = 1634 }); // 输出会员列表中的会员,这里将调用在Part类中重写的ToString方法 Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } // 检查列表中是否存在id为1734的会员。这里调用Part类中重写的IEquatable.Equals方法 // 它用来比较会员的id Console.WriteLine("\nContains(\"1734\"): {0}", parts.Contains(new Part { PartId = 1734, PartName = "" })); // 在第2个位置上添加一个会员 Console.WriteLine("\nInsert(2, \"1834\")"); parts.Insert(2, new Part() { PartName = "brake lever", PartId = 1834 }); //Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } Console.WriteLine("\nParts[3]: {0}", parts[3]); Console.WriteLine("\nRemove(\"1534\")"); // 这里将移除id为1534、名称为cogs的会员 // 虽然会员名称不一样,但是Equals方法只比较会员的id parts.Remove(new Part() { PartId = 1534, PartName = "cogs" }); Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } Console.WriteLine("\nRemoveAt(3)"); //这里将移除排在第4位的会员 parts.RemoveAt(3); Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } /* ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1434 Name: regular seat ID: 1444 Name: banana seat ID: 1534 Name: cassette ID: 1634 Name: shift lever Contains("1734"): False Insert(2, "1834") ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1834 Name: brake lever ID: 1434 Name: regular seat ID: 1444 Name: banana seat ID: 1534 Name: cassette ID: 1634 Name: shift lever Parts[3]: ID: 1434 Name: regular seat Remove("1534") ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1834 Name: brake lever ID: 1434 Name: regular seat ID: 1444 Name: banana seat ID: 1634 Name: shift lever RemoveAt(3) ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1834 Name: brake lever ID: 1444 Name: banana seat ID: 1634 Name: shift lever */ } }
常用属性和方法
下面的示例演示List<T>类型常用的几个属性和方法:
using System; using System.Collections.Generic; namespace ConsoleApp { class Program { static void Main(string[] args) { //创建泛型List类实例 List<string> strList = new List<string>(); //向List类实例中添加数据 strList.Add("10"); strList.Add("11"); strList.Add("12"); strList.Add("13"); //使用匿名委托显示字符串List的值 Action<string> Show = delegate (string strToShow) { Console.WriteLine("当前元素值{0}", strToShow); }; //在指定的位置插入元素 strList.Insert(2, "15"); //显示每个元素的值 strList.ForEach(Show); //未压缩前List的容量 Console.WriteLine("包含{0}个元素,可包含{1}个元素", strList.Count, strList.Capacity); //压缩List strList.TrimExcess(); //压缩后List的容量 Console.WriteLine("包含{0}个元素,可包含{1}个元素", strList.Count, strList.Capacity); //检查List中是否包含指定的元素 Console.WriteLine("是否包含数据\"15\":{0}", strList.Contains("15")); //使用索引值访问指定元素 Console.WriteLine("第2个元素:{0}", strList[2]); //从List中删除指定元素 bool result = strList.Remove("105"); //创建List的匿名委托 Converter<string, int> converter = delegate (string strIn) { return int.Parse(strIn); }; //使用匿名委托显示整型List的值 Action<int> print = delegate (int intToPrint) { Console.WriteLine("当前元素值:{0}", intToPrint); }; //使用匿名委托将字符串List类实例转换为整型List类实例 List<int> iList = strList.ConvertAll<int>(converter); //显示每个元素的值 iList.ForEach(print); Console.ReadLine(); /* 当前元素值10 当前元素值11 当前元素值15 当前元素值12 当前元素值13 包含5个元素,可包含8个元素 包含5个元素,可包含5个元素 是否包含数据"15":True 第3个元素:15 当前元素值10 当前元素值11 当前元素值15 当前元素值12 当前元素值13 */ } } }
线程安全
可以安全地执行多个读取 List<T> 的操作,但如果在读取时修改该集合,可能会出现问题。
若要确保线程安全,读取过程中锁定集合,或写入操作。 若要启用访问集合以进行读取和写入的多个线程,则必须实现自己的同步。 具有内置同步的集合,请参阅中的类 System.Collections.Concurrent 命名空间。 本质上是线程 – 安全替代方法,请参阅 ImmutableList<T> 类。
参考资料:
1.《C# 编程兵书》第7章
2. https://msdn.microsoft.com/zh-cn/library/6sh2ey19(v=vs.110).aspx