C# List代码实例详解:从入门到精通 – wiki基地

C# List 代码实例详解:从入门到精通

在C#编程中,List<T> 是一种非常常用的泛型集合,用于存储一组特定类型的对象。它提供了动态调整大小、高效的插入和删除操作以及丰富的操作方法。本文将深入探讨 List<T> 的各个方面,通过丰富的代码示例,帮助您从入门到精通地掌握它的用法。

一、List 基础

1.1 什么是 List?

List<T> 是 .NET Framework 中 System.Collections.Generic 命名空间下的一个泛型类。这里的 T 代表类型参数,您可以在创建 List 对象时指定具体的类型,例如 List<int> 用于存储整数,List<string> 用于存储字符串,List<MyClass> 用于存储自定义类的对象。

1.2 为什么使用 List?

与数组相比,List<T> 具有以下优势:

  • 动态大小: List<T> 的大小可以根据需要自动增长或缩小,无需手动管理内存。
  • 类型安全: List<T> 是泛型的,在编译时进行类型检查,避免了类型转换错误。
  • 丰富的API: List<T> 提供了大量的方法来添加、删除、查找、排序和操作元素。
  • 性能优化: List<T> 内部使用数组来存储数据,在大多数情况下具有良好的性能。

1.3 创建和初始化 List

有多种方法可以创建和初始化 List<T> 对象:

“`csharp
// 1. 创建一个空的 List
List numbers = new List();

// 2. 创建一个包含初始元素的 List
List names = new List() { “Alice”, “Bob”, “Charlie” };

// 3. 使用集合初始化器创建 List
List objects = new List()
{
new MyClass() { Name = “Object1” },
new MyClass() { Name = “Object2” }
};

// 4. 从现有数组创建 List
int[] array = { 1, 2, 3, 4, 5 };
List listFromArray = new List(array);

//5. 指定初始容量
List numbersWithCapacity = new List(10); // 初始容量为10
“`

  • 无参构造函数: 创建一个空的 List<T>,初始容量为0。当添加第一个元素时,容量会自动扩展。
  • 集合初始化器: 使用大括号 {} 直接在创建 List<T> 时添加初始元素。
  • IEnumerable<T> 参数的构造函数: 可以从任何实现了 IEnumerable<T> 接口的集合(如数组、其他 List 等)创建 List<T>
  • 指定初始容量: 使用 new List<int>(capacity) 可以指定初始的内部数组大小. 当知道 List 会容纳很多元素的时候,可以避免多次扩容带来的性能开销。

二、List 常用操作

2.1 添加元素

“`csharp
List numbers = new List();

// 1. 添加单个元素到末尾
numbers.Add(10);
numbers.Add(20);

// 2. 在指定位置插入元素
numbers.Insert(1, 15); // 在索引 1 的位置插入 15

// 3. 添加多个元素到末尾
numbers.AddRange(new int[] { 30, 40, 50 });

//4. InsertRange 插入多个元素
List moreNumbers = new List { 60, 70 };
numbers.InsertRange(3, moreNumbers); // 在索引3处插入moreNumbers
“`

  • Add(T item) 将指定的元素添加到 List<T> 的末尾。
  • Insert(int index, T item) 将指定的元素插入到 List<T> 的指定索引位置。
  • AddRange(IEnumerable<T> collection): 将指定集合的所有元素添加到List的末尾。
  • InsertRange(int index, IEnumerable<T> collection): 将指定集合的元素插入到List指定的位置

2.2 删除元素

“`csharp
List names = new List() { “Alice”, “Bob”, “Charlie”, “David”, “Bob” };

// 1. 删除指定元素(第一个匹配项)
names.Remove(“Bob”);

// 2. 删除指定索引位置的元素
names.RemoveAt(1); // 删除索引为 1 的元素 (现在是 Charlie)

// 3. 删除指定范围的元素
names.RemoveRange(0, 2); //删除从索引0开始的2个元素

// 4. 删除所有匹配条件的元素
names.RemoveAll(name => name.StartsWith(“A”)); // 删除所有以 “A” 开头的名字

// 5. 清空 List
names.Clear();
“`

  • Remove(T item)List<T> 中删除第一个与指定元素匹配的元素。
  • RemoveAt(int index) 删除 List<T> 中指定索引位置的元素。
  • RemoveRange(int index, int count): 删除从指定索引开始的一定数量的元素
  • RemoveAll(Predicate<T> match) 删除 List<T> 中所有满足指定条件的元素。
  • Clear() 清空 List<T> 中的所有元素。

2.3 查找元素

“`csharp
List numbers = new List() { 10, 20, 30, 40, 50, 30 };

// 1. 检查元素是否存在
bool contains20 = numbers.Contains(20); // true

// 2. 查找第一个匹配元素的索引
int indexOf30 = numbers.IndexOf(30); // 2

// 3. 查找最后一个匹配元素的索引
int lastIndexOf30 = numbers.LastIndexOf(30); // 5

// 4. 查找第一个满足条件的元素
int firstEven = numbers.Find(n => n % 2 == 0); // 10

// 5. 查找所有满足条件的元素
List evenNumbers = numbers.FindAll(n => n % 2 == 0); // { 10, 20, 30, 40, 30 }

// 6. 查找第一个满足条件的元素的索引
int firstEvenIndex = numbers.FindIndex(n => n % 2 == 0); // 0

// 7. 查找最后一个满足条件的元素的索引
int lastEvenIndex = numbers.FindLastIndex(n => n % 2 == 0); // 5
“`

  • Contains(T item) 检查 List<T> 是否包含指定的元素。
  • IndexOf(T item) 返回 List<T> 中第一个与指定元素匹配的元素的索引,如果未找到则返回 -1。
  • LastIndexOf(T item) 返回 List<T> 中最后一个与指定元素匹配的元素的索引,如果未找到则返回 -1。
  • Find(Predicate<T> match) 返回 List<T> 中第一个满足指定条件的元素,如果未找到则返回类型的默认值(例如,对于引用类型返回 null,对于整数返回 0)。
  • FindAll(Predicate<T> match) 返回一个包含 List<T> 中所有满足指定条件的元素的新 List<T>
  • FindIndex(Predicate<T> match) 返回 List<T> 中第一个满足指定条件的元素的索引,如果未找到则返回 -1。
  • FindLastIndex(Predicate<T> match) 返回 List<T> 中最后一个满足指定条件的元素的索引,如果未找到则返回 -1。

2.4 访问和修改元素

“`csharp
List names = new List() { “Alice”, “Bob”, “Charlie” };

// 1. 通过索引访问元素
string firstPerson = names[0]; // “Alice”

// 2. 通过索引修改元素
names[1] = “Bobby”;

//3. 获取List的长度
int count = names.Count;

//4. 获取容量
int capacity = names.Capacity;
“`

  • 索引器 [] 可以像访问数组一样,通过索引访问和修改 List<T> 中的元素。
  • Count 属性: 获取 List<T> 中实际包含的元素个数。
  • Capacity 属性: 获取或设置 List<T> 的内部数组容量.

2.5 排序和反转

“`csharp
List numbers = new List() { 5, 2, 8, 1, 9, 4 };

// 1. 升序排序
numbers.Sort(); // { 1, 2, 4, 5, 8, 9 }

// 2. 降序排序
numbers.Sort((a, b) => b.CompareTo(a)); // { 9, 8, 5, 4, 2, 1 }

// 3. 使用自定义比较器排序
numbers.Sort(new MyComparer());

// 4. 反转 List
numbers.Reverse(); // { 1, 2, 4, 5, 8, 9 }
// 5. 部分反转
numbers.Reverse(1, 3); // 从索引1开始,反转3个元素
“`

  • Sort()List<T> 中的元素进行升序排序(元素类型必须实现 IComparable<T> 接口)。
  • Sort(Comparison<T> comparison) 使用指定的比较委托对 List<T> 中的元素进行排序。
  • Sort(IComparer<T> comparer) 使用指定的比较器对 List<T> 中的元素进行排序。
  • Reverse() 反转 List<T> 中元素的顺序。
  • Reverse(int index, int count): 反转指定范围的元素

csharp
// 自定义比较器示例
public class MyComparer : IComparer<int>
{
public int Compare(int x, int y)
{
// 按奇偶性排序,偶数在前,奇数在后
if (x % 2 == 0 && y % 2 != 0)
{
return -1;
}
else if (x % 2 != 0 && y % 2 == 0)
{
return 1;
}
else
{
return x.CompareTo(y);
}
}
}

2.6 其他常用方法

“`csharp
List numbers = new List() { 1, 2, 3, 4, 5 };

// 1. 将 List 转换为数组
int[] array = numbers.ToArray();

// 2. 复制 List 的一部分到一个新 List
List subList = numbers.GetRange(1, 3); // { 2, 3, 4 }

// 3. 判断 List 是否为空
bool isEmpty = numbers.Count == 0; // false

// 4. 对 List 中的每个元素执行指定操作
numbers.ForEach(n => Console.WriteLine(n * 2));

//5. Exists: 检查是否存在符合条件的元素
bool existsGreaterThan5 = numbers.Exists(x => x > 5); //false

//6. TrueForAll: 检查是否所有元素都符合条件
bool allPositive = numbers.TrueForAll(x => x > 0); // true

//7. BinarySearch 二分查找
numbers.Sort();
int index = numbers.BinarySearch(3); //index = 2. 必须先排序
“`

  • ToArray()List<T> 转换为一个包含相同元素的数组。
  • GetRange(int index, int count) 返回一个包含 List<T> 中指定范围元素的新 List<T>
  • ForEach(Action<T> action)List<T> 中的每个元素执行指定的操作。
  • Exists(Predicate<T> match): 检查 List<T> 中是否存在满足指定条件的元素.
  • TrueForAll(Predicate<T> match): 检查是否所有元素都符合指定条件
  • BinarySearch(T item): 使用二分查找算法在已排序的 List<T> 中查找指定的元素,如果找到则返回元素的索引,否则返回一个负数。

三、List 高级应用

3.1 使用 LINQ 查询 List

LINQ (Language Integrated Query) 是一种强大的查询语言,可以方便地对各种数据源(包括 List<T>)进行查询、筛选、排序、分组等操作。

“`csharp
List people = new List()
{
new Person() { Name = “Alice”, Age = 30, City = “New York” },
new Person() { Name = “Bob”, Age = 25, City = “Los Angeles” },
new Person() { Name = “Charlie”, Age = 35, City = “New York” },
new Person() { Name = “David”, Age = 28, City = “Chicago” }
};

// 1. 查询年龄大于 30 的人
var peopleOver30 = from p in people
where p.Age > 30
select p;

// 2. 按年龄升序排序
var peopleSortedByAge = from p in people
orderby p.Age
select p;

// 3. 按城市分组
var peopleByCity = from p in people
group p by p.City;

// 4. 投影到新的匿名类型
var namesAndAges = from p in people
select new { p.Name, p.Age };

// 使用方法语法的等效查询
var peopleOver30_2 = people.Where(p => p.Age > 30);
var peopleSortedByAge_2 = people.OrderBy(p => p.Age);
var peopleByCity_2 = people.GroupBy(p => p.City);
var namesAndAges_2 = people.Select(p => new { p.Name, p.Age });
“`
通过 LINQ,可以以声明式的方式对List进行复杂查询,大大简化代码。LINQ 提供了两种主要的语法风格:查询语法(类似 SQL)和方法语法(使用扩展方法)。

3.2 List 与多线程

在多线程环境下使用 List<T> 需要特别注意线程安全问题。List<T> 本身不是线程安全的,如果多个线程同时对同一个 List<T> 对象进行读写操作,可能会导致数据不一致或其他不可预测的错误。

以下是一些常用的多线程安全解决方案:

  1. 使用锁 (lock): 在访问 List<T> 的代码块前后使用 lock 语句,确保同一时间只有一个线程可以访问 List<T>

“`csharp
private List numbers = new List();
private object listLock = new object();

public void AddNumber(int number)
{
lock (listLock)
{
numbers.Add(number);
}
}

public int GetNumber(int index)
{
lock (listLock)
{
return numbers[index];
}
}
“`

  1. 使用线程安全的集合: .NET Framework 提供了 System.Collections.Concurrent 命名空间,其中包含一些线程安全的集合类,例如 ConcurrentBag<T>ConcurrentQueue<T>ConcurrentDictionary<TKey, TValue> 等。如果需要线程安全的列表功能, 且操作主要为增加和读取,可考虑使用 ConcurrentBag<T> 代替 List<T>

  2. 创建 List 的副本: 如果一个线程需要读取 List<T> 的数据,而另一个线程可能修改它,可以考虑创建 List<T> 的副本(例如使用 ToList() 方法),这样读取线程可以在副本上操作,避免线程冲突。

3.3 List 的性能优化

  • 预估容量: 在创建 List<T> 时,如果能预估到大致的元素数量,可以通过构造函数指定初始容量,减少 List<T> 内部数组的扩容次数,提高性能。
  • 避免频繁的插入和删除:List<T> 中间位置插入或删除元素会导致后续元素的移动,如果需要频繁地在中间位置插入或删除元素,可以考虑使用 LinkedList<T>
  • 使用 AddRangeInsertRange 批量添加或插入多个元素时,使用 AddRangeInsertRange 方法比循环调用 AddInsert 方法更高效。
  • 使用 BinarySearch 前先排序

四、总结

List<T> 是 C# 中非常重要和常用的集合类型,掌握它的用法对于编写高效、可靠的 C# 代码至关重要。本文通过丰富的代码示例,详细介绍了 List<T> 的基础知识、常用操作、高级应用和性能优化技巧,希望能够帮助您更好地理解和使用 List<T>

请记住,实践是掌握任何编程技能的最佳途径。建议您在学习过程中多动手编写代码,尝试不同的 List<T> 操作,并结合实际项目需求进行应用,才能真正做到融会贯通。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部