【C#】泛型集合
C# 泛型集合提供了类型安全和性能优势,因为它们在编译时就确定了存储元素的类型。
1. List<T>
在C#中,List<T> 是一个非常有用的泛型集合类,它实现了 IList<T> 接口,提供了一系列方法来操作对象列表。List<T> 是动态数组的实现,允许你添加、移除和访问列表中的元素。
1.1. 创建 List<T> 实例
1 List<int> numbers = new List<int>();
1.2. 向 List<T> 添加元素
1 // 使用 Add 方法 2 numbers.Add(1); 3 numbers.Add(2); 4 5 // 使用 AddRange 方法添加多个元素 6 List<int> moreNumbers = new List<int> { 3, 4, 5 }; 7 numbers.AddRange(moreNumbers);
1.3. 访问 List<T> 中的元素
1 // 使用索引访问,索引从0开始 2 int firstNumber = numbers[0];
1.4. 遍历 List<T>
1 foreach (int number in numbers) 2 { 3 Console.WriteLine(number); 4 }
1.5. 移除 List<T> 中的元素
1 // 按值移除 2 bool removed = numbers.Remove(2); 3 4 // 按索引移除 5 numbers.RemoveAt(0);
1.6. List<T> 的搜索和排序
1 // 查找特定项的索引 2 int index = numbers.IndexOf(3); 3 4 // 存在性检查 5 bool contains = numbers.Contains(4); 6 7 // 对列表进行排序 8 numbers.Sort();
1.7. List<T> 的容量和截断
1 // 获取列表中的项目数 2 int count = numbers.Count; 3 4 // 清空列表 5 numbers.Clear(); 6 7 // 截断列表,移除特定位置之后的元素 8 numbers.RemoveRange(2, numbers.Count - 2);
1.8. 使用 List<T> 的泛型能力
1 List<Person> people = new List<Person>(); 2 Person person = new Person("John", 30); 3 people.Add(person);
在这个例子中,Person 是一个用户定义的类型,List<Person> 能够存储 Person 类型的对象。
1.9. 注意
- List<T> 是一个可变大小的集合,这意味着你可以在运行时向它添加和移除元素。
- List<T> 提供了类型安全,这意味着只有指定类型的元素才能被添加到列表中。
- 泛型集合的性能通常比非泛型集合(如 ArrayList)更好,因为它们使用了类型安全的数组实现,并且不需要类型转换。
- List<T> 是C#中处理集合和列表操作的一个非常有用的工具,它的灵活性和强大的功能使它成为C#编程中的首选集合类型之一。
2. Dictionary<TKey, TValue>
在C#中,Dictionary<TKey, TValue> 是一个泛型集合类,它存储了一组成对的键值对,其中每个键与一个值相关联。
这个类实现了 IDictionary<TKey, TValue> 接口,并且位于 System.Collections.Generic 命名空间中。
2.1. 创建 Dictionary<TKey, TValue> 实例
1 Dictionary<string, int> ages = new Dictionary<string, int>();
2.2. 向 Dictionary<TKey, TValue> 添加键值对
1 // 使用 Add 方法 2 ages.Add("Alice", 25); 3 ages.Add("Bob", 30); 4 5 // 使用索引器添加或更新键值对 6 ages["Charlie"] = 35;
2.3. 访问 Dictionary<TKey, TValue> 中的元素
1 // 使用键获取值 2 int aliceAge = ages["Alice"]; 3 4 // 使用 TryGetValue 方法安全地获取值 5 int age; 6 if (ages.TryGetValue("Bob", out age)) 7 { 8 Console.WriteLine(age); 9 }
2.4. 遍历 Dictionary<TKey, TValue>
1 foreach (var kvp in ages) 2 { 3 Console.WriteLine(kvp.Key + " is " + kvp.Value + " years old."); 4 }
分别遍历键和值:
1 foreach (string name in ages.Keys) 2 { 3 Console.WriteLine(name); 4 } 5 6 foreach (int age in ages.Values) 7 { 8 Console.WriteLine(age); 9 }
2.5. 移除 Dictionary<TKey, TValue> 中的元素
1 // 使用 Remove 方法 2 bool removed = ages.Remove("Alice"); 3 4 // 使用索引器移除(如果键不存在,则不抛出异常) 5 ages["Bob"] = default(int); // 这将从 Dictionary 中移除 "Bob"
2.6. Dictionary<TKey, TValue> 的键值检查
1 // 检查键是否存在 2 bool containsKey = ages.ContainsKey("Charlie"); 3 4 // 检查值是否存在 5 bool containsValue = ages.ContainsValue(35);
2.7. Dictionary<TKey, TValue> 的容量
1 // 获取字典中的项目数 2 int count = ages.Count; 3 4 // 清空字典 5 ages.Clear();
2.8. 注意
- 键必须唯一,但值不必唯一。
- 键和值的类型由泛型参数 TKey 和 TValue 指定,这提供了类型安全。
- 尝试访问不存在的键将导致 KeyNotFoundException,使用 TryGetValue 方法可以避免此异常。
- Dictionary<TKey, TValue> 不是线程安全的。如果需要在多线程环境中使用,可以使用 ConcurrentDictionary<TKey, TValue> 或者在操作字典时使用适当的同步机制。
- Dictionary<TKey, TValue> 是处理需要通过键快速查找、插入和删除数据的场景的理想选择,例如缓存实现、计数器或任何需要映射关系的情况。
3. HashSet<T>
在C#中,HashSet<T> 是一个泛型集合,它根据元素的哈希码存储不重复的元素,并提供快速地添加、删除和查找操作。
HashSet<T> 位于 System.Collections.Generic 命名空间中。
3.1. 创建 HashSet<T> 实例
1 HashSet<int> numbers = new HashSet<int>();
3.2. 向 HashSet<T> 添加元素
1 // 使用 Add 方法 2 numbers.Add(1); 3 numbers.Add(2); 4 5 // 尝试添加已存在的元素,不会重复添加 6 numbers.Add(1); // 无效果
3.3. 访问 HashSet<T> 中的元素
由于 HashSet<T> 是无序的,你不能像列表那样通过索引访问元素。但你可以使用 Contains 方法检查元素是否存在:
1 bool contains = numbers.Contains(2);
3.4. 遍历 HashSet<T>
1 foreach (int number in numbers) 2 { 3 Console.WriteLine(number); 4 }
3.5. 移除 HashSet<T> 中的元素
1 // 使用 Remove 方法 2 bool removed = numbers.Remove(2); 3 4 // 清空 HashSet 5 numbers.Clear();
3.6. HashSet<T> 的搜索
1 // 查找元素是否存在 2 bool exists = numbers.Contains(1);
3.7. HashSet<T> 的集合操作
HashSet<T> 提供了一些集合操作的方法,如并集、交集、差集等:
1 HashSet<int> set1 = new HashSet<int> { 1, 2, 3 }; 2 HashSet<int> set2 = new HashSet<int> { 2, 3, 4 }; 3 4 // 并集 5 HashSet<int> union = new HashSet<int>(set1); 6 union.UnionWith(set2); 7 8 // 交集 9 HashSet<int> intersection = new HashSet<int>(set1); 10 intersection.IntersectWith(set2); 11 12 // 差集 13 HashSet<int> difference = new HashSet<int>(set1); 14 difference.ExceptWith(set2);
3.8. 注意
- HashSet<T> 中的元素必须是唯一的,即不会包含重复项。
- 元素的顺序在 HashSet<T> 中是不固定的,因为集合是基于哈希函数来存储元素的。
- 要使 HashSet<T> 正常工作,元素类型必须重写 GetHashCode 和 Equals 方法。
- HashSet<T> 不是线程安全的。如果需要在多线程环境中使用,可以考虑使用 ConcurrentDictionary<TKey, TValue> 或者在操作集合时使用适当的同步机制。
- HashSet<T> 是处理需要存储一组唯一项的场景的理想选择,例如,存储一组无需排序的唯一标识符或执行集合操作。
4. SortedDictionary<TKey, TValue>
在C#中,SortedDictionary<TKey, TValue> 是一个泛型集合类,它存储键值对并根据键的排序对它们进行排序。
与 Dictionary<TKey, TValue> 类似,它实现了键值对的映射,但不同的是,SortedDictionary<TKey, TValue> 会根据键的默认比较器(或你提供的 IComparer<TKey> 实例)自动保持键值对的排序。
4.1. 创建 SortedDictionary<TKey, TValue> 实例
1 SortedDictionary<string, int> sortedDict = new SortedDictionary<string, int>();
4.2. 向 SortedDictionary<TKey, TValue> 添加键值对
1 // 使用 Add 方法 2 sortedDict.Add("apple", 1); 3 sortedDict.Add("banana", 2); 4 5 // 使用索引器添加或更新键值对 6 sortedDict["cherry"] = 3;
4.3. 访问 SortedDictionary<TKey, TValue> 中的元素
1 // 使用键获取值 2 int value = sortedDict["banana"]; // 返回 2 3 4 // 使用 TryGetValue 方法安全地获取值 5 if (sortedDict.TryGetValue("cherry", out int cherryValue)) 6 { 7 Console.WriteLine(cherryValue); // 输出 3 8 }
4.4. 遍历 SortedDictionary<TKey, TValue>
1 foreach (var kvp in sortedDict) 2 { 3 Console.WriteLine(kvp.Key + " : " + kvp.Value); 4 }
4.5. 移除 SortedDictionary<TKey, TValue> 中的元素
1 // 使用 Remove 方法 2 bool removed = sortedDict.Remove("apple"); 3 4 // 使用索引器移除(如果键不存在,则不抛出异常) 5 sortedDict["banana"] = default(int); // 这将从 SortedDictionary 中移除 "banana"
4.6. SortedDictionary<TKey, TValue> 的键值检查
1 // 检查键是否存在 2 bool containsKey = sortedDict.ContainsKey("cherry"); 3 4 // 检查值是否存在 5 bool containsValue = sortedDict.ContainsValue(3);
4.7. SortedDictionary<TKey, TValue> 的容量
1 // 获取字典中的项目数 2 int count = sortedDict.Count; 3 4 // 清空字典 5 sortedDict.Clear();
4.8. 使用自定义的比较器
1 SortedDictionary<string, int> sortedDict = new SortedDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
在这个例子中,我们使用 StringComparer.OrdinalIgnoreCase 作为比较器,这样字典会以不区分大小写的方式对键进行排序。
4.9. 注意
- SortedDictionary<TKey, TValue> 中的键必须是唯一的,并且会按照键的排序顺序存储。
- 类似于 Dictionary<TKey, TValue>,SortedDictionary<TKey, TValue> 也允许空键和空值,但要注意空键也只能有一个。
- 由于维护排序的特性,SortedDictionary<TKey, TValue> 在添加和删除操作上可能比 Dictionary<TKey, TValue> 慢,特别是在大型集合上。
- SortedDictionary<TKey, TValue> 是处理需要保持键的排序顺序的场景的理想选择,例如,当你需要按照键排序来遍历键值对或者需要快速定位最大或最小的键时。
5. SortedList<TKey, TValue>
在C#中,SortedList<TKey, TValue> 是一个同时具备字典和列表特性的泛型集合类。它存储键值对,并根据键的默认比较器(或用户提供的 IComparer<TKey> 实现)自动保持键的排序。
与 SortedDictionary<TKey, TValue> 类似,SortedList<TKey, TValue> 也是按照键排序的,但它同时提供了通过索引访问元素的能力。
5.1. 创建 SortedList<TKey, TValue> 实例
1 SortedList<string, int> sortedList = new SortedList<string, int>();
5.2. 向 SortedList<TKey, TValue> 添加键值对
1 // 使用 Add 方法 2 sortedList.Add("apple", 1); 3 sortedList.Add("banana", 2); 4 5 // 使用索引器添加或更新键值对 6 sortedList["cherry"] = 3;
5.3. 访问 SortedList<TKey, TValue> 中的元素
1 // 使用键获取值 2 int value = sortedList["banana"]; // 返回 2 3 4 // 使用索引访问(返回键值对) 5 DictionaryEntry entry = sortedList[0]; // 获取第一个键值对
5.4. 遍历 SortedList<TKey, TValue>
1 foreach (KeyValuePair<string, int> kvp in sortedList) 2 { 3 Console.WriteLine(kvp.Key + " : " + kvp.Value); 4 } 5 6 // 或者使用 DictionaryEntry 7 foreach (DictionaryEntry de in sortedList) 8 { 9 Console.WriteLine(de.Key + " : " + de.Value); 10 }
5.5. 移除 SortedList<TKey, TValue> 中的元素
1 // 使用 Remove 方法 2 bool removed = sortedList.Remove("apple"); 3 4 // 使用索引器移除(如果键不存在,则抛出异常) 5 sortedList.RemoveAt(0); // 移除第一个键值对
5.6. SortedList<TKey, TValue> 的键值检查
1 // 检查键是否存在 2 bool containsKey = sortedList.ContainsKey("cherry"); 3 4 // 检查值是否存在 5 bool containsValue = sortedList.ContainsValue(3);
5.7. SortedList<TKey, TValue> 的容量
1 // 获取列表中的项目数 2 int count = sortedList.Count; 3 4 // 清空列表 5 sortedList.Clear();
5.8. 使用自定义的比较器
1 SortedList<string, int> sortedList = new SortedList<string, int>(StringComparer.OrdinalIgnoreCase);
在这个例子中,我们使用 StringComparer.OrdinalIgnoreCase 作为比较器,这样 SortedList 会以不区分大小写的方式对键进行排序。
5.9. 注意
- SortedList<TKey, TValue> 中的键必须是唯一的,并且会按照键的排序顺序存储。
- 由于 SortedList<TKey, TValue> 既实现了 IDictionary<TKey, TValue> 也实现了 IList 接口,所以它既可以通过键也可以通过索引来访问元素。
- 访问元素时,如果使用索引,SortedList<TKey, TValue> 提供了 DictionaryEntry 的集合,它包含键和值。
- 由于维护排序的特性,SortedList<TKey, TValue> 在添加和删除操作上可能比非排序的 List<TKey, TValue> 慢,特别是在大型集合上。
- SortedList<TKey, TValue> 是处理需要保持键的排序顺序并且需要通过索引快速访问元素的场景的理想选择。
6. Queue<T>
在C#中,Queue<T> 是一个泛型集合类,实现了先进先出(FIFO)的数据结构,位于 System.Collections.Generic 命名空间中。这意味着第一个添加到队列中的元素将是第一个被移除的元素。
6.1. 创建 Queue<T> 实例
1 Queue<int> numberQueue = new Queue<int>();
6.2. 向 Queue<T> 添加元素
1 // 使用 Enqueue 方法在队列尾部添加元素 2 numberQueue.Enqueue(1); 3 numberQueue.Enqueue(2); 4 numberQueue.Enqueue(3);
6.3. 访问 Queue<T> 中的元素
1 // 查看队列头部的元素但不移除 2 int frontItem = numberQueue.Peek(); // 返回 1
6.4. 遍历 Queue<T>
1 foreach (int item in numberQueue) 2 { 3 Console.WriteLine(item); 4 }
6.5. 移除 Queue<T> 中的元素
1 // 使用 Dequeue 方法移除队列头部的元素 2 int dequeuedItem = numberQueue.Dequeue(); // 返回并移除 1
6.6. 查看 Queue<T> 中的元素数量
1 int count = numberQueue.Count; // 返回当前队列中的元素数量
6.7. 清空 Queue<T>
1 numberQueue.Clear(); // 移除队列中的所有元素
6.8. 注意
- 队列的头部由 Peek 和 Dequeue 方法操作,而队列的尾部由 Enqueue 方法操作。
- 尝试访问队列中不存在的元素(例如,队列为空时调用 Dequeue 或 Peek)将抛出 InvalidOperationException异常。
- 队列是后进先出的集合,这使得它们非常适合用于跟踪按特定顺序处理的元素。
- 在多线程环境中,如果需要线程安全的队列操作,可以使用 System.Collections.Concurrent 命名空间下的 ConcurrentQueue<T> 类。这个类是线程安全的,并且设计用于高并发场景。
- Queue<T> 是处理需要保持添加顺序的数据集合时的理想选择,例如任务调度、事件处理队列等。
7. Stack<T>
在C#中,Stack<T> 是一个泛型集合类,实现了后进先出(LIFO)的数据结构,位于 System.Collections.Generic 命名空间中。这意味着最后添加到栈中的元素将是第一个被移除的元素。
7.1. 创建 Stack<T> 实例
1 Stack<int> numberStack = new Stack<int>();
7.2. 向 Stack<T> 添加元素
1 // 使用 Push 方法在栈顶添加元素 2 numberStack.Push(1); 3 numberStack.Push(2); 4 numberStack.Push(3);
7.3. 访问 Stack<T> 中的元素
1 // 查看栈顶的元素但不移除 2 int topItem = numberStack.Peek(); // 返回 3
7.4. 遍历 Stack<T>
1 foreach (int item in numberStack) 2 { 3 Console.WriteLine(item); // 输出顺序会是 3, 2, 1 4 }
7.5. 移除 Stack<T> 中的元素
1 // 使用 Pop 方法移除栈顶的元素 2 int poppedItem = numberStack.Pop(); // 返回并移除 3
7.6. 查看 Stack<T> 中的元素数量
1 int count = numberStack.Count; // 返回当前栈中的元素数量
7.7. 清空 Stack<T>
1 numberStack.Clear(); // 移除栈中的所有元素
7.8. 注意
- 栈的顶部由 Peek 和 Pop 方法操作,而 Push 方法用于在栈顶添加元素。
- 尝试 Pop 或 Peek 一个空栈将抛出 InvalidOperationException异常。
- 栈常用于算法和数据结构中,如表达式求值、回溯算法、撤销操作的实现等。
- 在多线程环境中,如果需要线程安全的栈操作,可以使用 System.Collections.Concurrent 命名空间下的 ConcurrentStack<T> 类。这个类是线程安全的,并且设计用于高并发场景。
- Stack<T> 是处理需要保持添加顺序的逆序数据集合时的理想选择。与 List<T> 或 Queue<T> 相比,Stack<T> 提供了更明确的添加和移除操作,即仅在集合的一端进行操作。
8. 总结
- 泛型集合的性能通常优于非泛型集合,因为它们避免了装箱和拆箱操作,并且提供了更好的类型检查。
- 泛型集合要求键和值的类型在创建集合时就确定下来,这提供了更好的类型安全性。
- 泛型集合类位于 System.Collections.Generic 命名空间中。
- 泛型集合是C#中处理集合和键值对数据结构的强大工具,它们在现代C#应用程序中被广泛使用
时间:2024年4月29日

【C#】泛型集合
浙公网安备 33010602011771号