赞
踩
本文主要讨论:List(列表),Dictionary(字典) ,Queue(队列) ,Stack(栈),HashSet(唯一集合),LinkedList(双向链表),BitArray(位数组),SortedSet(排序集合),ObservableCollection(可观察到的集合(机翻),我喜欢称为可观集合 ), Queue(队列),ConcurrentDictionary<K, V>(多线程字典(机翻是并发字典 )),Immutable Collections( 不可变的集合),Custom Collections(自定义集合)。
从C#的角度来看,Array不属于集合(Collections)类别。在C#中,集合类通常指的是实现了泛型集合接口(例如 ICollection<T>)的可变大小的数据结构,如List<T>, Dictionary<K, V>, HashSet<T>, Queue<T>, Stack<T>, LinkedList<T> 等。
集合类指.NET Framework 中的静态类 System.Collections,它包含了一系列用于处理和操作集合的类和接口。
与集合类不同,Array 是一种不可变大小的数组数据结构,元素类型相同,具有固定的长度。虽然数组在C#中非常常见,而且具有高效的元素访问速度,但它们在大小上是固定的,不支持动态添加或删除元素,因此在概念上不被视为集合类。
尽管如此,在实际编程中,数组(Array)和集合(Collections)都用于存储和管理数据,开发者根据具体需求选择使用哪种数据结构,以满足他们的编程目标。
C# 提供了丰富的集合数据结构,每种都具有不同的特点和用途,最重要的是性能和复杂度等。以下是一些常见的 C# 集合数据结构以及它们的特点:
特点:动态大小的列表,可存储任何类型的元素,支持索引访问。
适用于:动态数组,用于存储和管理元素列表。
List<int> numbers = new List<int>();
// 添加元素
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
特点:键值对存储结构,快速查找和存储数据,键唯一。
适用于:存储和管理键值对,例如配置、映射、缓存。
在Dictionary<string, int> dictionary = new Dictionary<string, int>();
dictionary.Add("apple", 1);
dictionary.Add("banana", 2);
特点:队列和栈数据结构,支持先进先出 (FIFO) 和后进先出 (LIFO) 操作。
适用于:任务排队、处理消息、遍历数据。
using System; using System.Collections.Generic; class Program { static void Main() { Queue<string> tasks = new Queue<string>(); // 入队 tasks.Enqueue("Task 1"); tasks.Enqueue("Task 2"); tasks.Enqueue("Task 3"); // 出队 while (tasks.Count > 0) { string task = tasks.Dequeue(); Console.WriteLine("Processing: " + task); } --------------------------- --------------------------- Stack<int> numbers = new Stack<int>(); // 压栈 numbers.Push(1); numbers.Push(2); numbers.Push(3); // 弹栈 while (numbers.Count > 0) { int number = numbers.Pop(); Console.WriteLine("Popped: " + number); } } }
特点:双向链表,用于频繁插入和删除操作,不适用于随机访问。
适用于:频繁插入和删除的场景,如某些算法。
using System; using System.Collections.Generic; class Program { static void Main() { LinkedList<string> names = new LinkedList<string>(); // 添加元素 names.AddLast("Alice"); names.AddLast("Bob"); names.AddLast("Charlie"); // 遍历链表 Console.WriteLine("LinkedList Elements:"); foreach (string name in names) { Console.WriteLine(name); } } }
特点:位数组,用于处理位级别的操作。
适用于:处理大量布尔值的情况,如位图索引。
using System; using System.Collections; class Program { static void Main() { // 创建一个 BitArray,初始大小为 8 BitArray bits = new BitArray(8); // 设置一些位 bits.Set(0, true); bits.Set(1, false); bits.Set(2, true); // 输出所有位 for (int i = 0; i < bits.Length; i++) { Console.WriteLine($"Bit {i}: {bits[i]}"); } } }
无序集合,用于存储不重复的元素,基于哈希表实现。
适用场景:需要快速查找、添加、删除元素,且元素
确保元素的唯一性,适用于去重操作。
HashSet<int> hashSet = new HashSet<int>();
hashSet.Add(1);
hashSet.Add(2);
hashSet.Add(3);
特点:有序集合,自动排序元素。
适用于:需要元素有序的场景。
SortedSet<int> sortedSet = new SortedSet<int>();
sortedSet.Add(3);
sortedSet.Add(1);
sortedSet.Add(2);
特点:可观察的集合,用于绑定到界面元素并在数据更改时通知
1.数据变更通知:当集合中的项目被添加、删除或整个集合被刷新时,它会触发 CollectionChanged 事件。
2.自动更新UI:在数据绑定的场景中,UI会自动响应集合的变化,反映最新的数据。
3.线程安全性:需要注意的是,ObservableCollection<T> 本身不是线程安全的。如果在多线程环境中操作,需要额外的同步机制。。
适用于:适用于数据绑定,特别是在UI编程中或者WPF 以及其他 XAML 应用的数据绑定。
1.MVVM模式中的数据绑定:在 WPF、Xamarin 或其他使用 MVVM 模式的应用程序中,当模型或视图模型的数据发生变化需要自动更新到UI界面时。
2.需要监视集合更改的场景:在任何需要知道集合何时被修改的情况下
using System; using System.Collections.ObjectModel; public class MainClass { public static void Main() { // 创建一个 ObservableCollection ObservableCollection<string> names = new ObservableCollection<string>(); // 添加事件处理器 names.CollectionChanged += Names_CollectionChanged; // 向集合中添加元素 names.Add("John"); names.Add("Jane"); } private static void Names_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // 当集合改变时,这里的代码会被执行 Console.WriteLine("Collection changed: " + e.Action.ToString()); } }
特点:存储委托或函数,可用于实现事件队列。
适用于:处理事件队列,例如动画序列。
Queue Action是 C# 中基于 System.Collections.Generic.Queue 类的一个特殊用法,用于存储 Action 类型的对象,即无返回值的委托。它按照先进先出(FIFO)的顺序管理这些委托。
先进先出:元素按照它们被添加的顺序出队。
用于委托管理:专门用于存储和管理 Action 委托。
线程安全性:标准的 Queue 不是线程安全的,需要额外机制来处理多线程。
适用于任务需要按顺序执行的场景,如事件处理或任务调度。
using System; using System.Collections.Generic; class Program { static void Main() { Queue<Action> queue = new Queue<Action>(); // 添加任务 queue.Enqueue(() => Console.WriteLine("First Task")); queue.Enqueue(() => Console.WriteLine("Second Task")); // 执行任务 while (queue.Count > 0) { Action task = queue.Dequeue(); task(); } } }
特点:多线程安全的字典,支持并发操作。
适用于:多线程环境下的键值对存储和操作。
ConcurrentDictionary<K, V> 是 System.Collections.Concurrent 命名空间中的一个线程安全的字典集合。
线程安全:可在多线程环境下安全使用。
高性能:为并发操作优化,比使用锁定的标准 Dictionary 效率更高。
原子操作:提供原子级别的添加、移除等操作。
多线程数据共享:在多线程应用中共享和修改键值对。
using System; using System.Collections.Concurrent; class Program { static void Main() { ConcurrentDictionary<string, int> concurrentDictionary = new ConcurrentDictionary<string, int>(); // 添加元素 concurrentDictionary.TryAdd("key1", 1); concurrentDictionary.TryAdd("key2", 2); // 访问元素 Console.WriteLine(concurrentDictionary["key1"]); } }
特点:不可变的集合,一旦创建就无法修改,线程安全。
适用于:需要保持不变性的情况,函数式编程。
不可变集合是 System.Collections.Immutable 命名空间中的一系列集合类,一旦创建就不能更改。
不可变性:集合创建后,其内容不可更改。
线程安全:由于不可变,天然线程安全。
副作用减少:不会因修改集合而影响其他引用该集合的代码。
适用场景
并发编程:在多线程环境中共享集合数据。
函数式编程风格:避免副作用和可变状态。
using System; using System.Collections.Immutable; class Program { static void Main() { var immutableList = ImmutableList.Create<int>(); immutableList = immutableList.Add(1); immutableList = immutableList.Add(2); foreach (var item in immutableList) { Console.WriteLine(item); } } }
灵活性:可以根据需求定制集合的行为。
功能定制:可以实现特定的数据结构和算法。
适用场景
特殊需求:标准库中的集合不满足特定需求时。
优化性能:针对特定场景优化性能。
///假设我们要实现一个简单的自定义循环队列: using System; using System.Collections; using System.Collections.Generic; public class CircularQueue<T> : IEnumerable<T> { private T[] _items; private int _head; private int _tail; public CircularQueue(int capacity) { _items = new T[capacity]; _head = -1; _tail = -1; } public void Enqueue(T item) { // 实现循环队列的入队逻辑 } public T Dequeue() { // 实现循环队列的出队逻辑 } public IEnumerator<T> GetEnumerator() { // 实现循环队列的迭代器 } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
还有很多比较特殊的集合类,但是本文所列的这些在传统集合具有代表性,供各位参考,代码示例比较简单,以后有时间会完善。
(本文部分内容参考自《C#本质论8.0》Mark Michaelis)
提供原子级别的添加、移除等操作。
原子操作是指在多线程编程中,一个操作在开始到结束的过程中不会被其他线程中断的操作。
在多线程环境当我们说 ConcurrentDictionary<K, V> 提供了原子级别的添加、移除等操作时,我们是指:中,原子操作非常重要,因为它们帮助避免竞态条件,确保数据的一致性和完整性。
原子级添加和移除:
例如,当你使用 TryAdd 方法向 ConcurrentDictionary 添加一个键值对时,整个添加过程是不可分割的。不会有两个线程同时添加同一个键,并且在任何时间点,其他线程看到的字典都是一致的状态。
同样地,使用 TryRemove 移除元素时,整个移除过程也是原子的。
线程安全性:
这意味着在多线程环境中,即使多个线程同时尝试添加或移除字典中的元素,ConcurrentDictionary 也会确保每个操作都是完整执行的,不会相互干扰,也不会导致数据不一致的问题。
无需外部同步:
在使用普通的 Dictionary<K, V> 时,你通常需要使用锁(如 lock 语句)来确保添加或移除操作的线程安全。但在 ConcurrentDictionary 中,这种同步机制已经内置,无需额外的锁定。
示例如下
using System; using System.Collections.Concurrent; using System.Threading.Tasks; class Program { static void Main() { var dict = new ConcurrentDictionary<int, string>(); // 同时启动多个任务添加元素 Parallel.For(0, 1000, (i) => { dict.TryAdd(i, $"Value{i}"); }); // 此时,dict 中应该恰好有 1000 个元素 Console.WriteLine($"Number of items: {dict.Count}"); } }
1.不可变性:数据对象在被创建后不允许被修改,而是通过创建新的对象来表示数据的变化。
2.纯函数:函数的输出仅依赖于输入,并且不产生副作用(如修改全局状态或输入参数)。
3.高阶函数:函数可以作为参数传递给其他函数,或者作为结果返回。
4.递归:倾向于使用递归而非循环来实现重复操作。
与其他编程风格的比较
命令式编程:
关注如何实现(步骤和过程)。
示例:使用循环和条件语句来操作变量。
List<int> doubled = new List<int>();
foreach (var number in numbers)
{
doubled.Add(number * 2);
}
面向对象编程:
将数据和行为封装在对象中。
示例:使用类和对象来组织代码,强调继承、封装和多态。(这个我就不多说了,相信学习C#各位已经掌握的炉火纯青)
函数式编程:
关注做什么(描述问题的解决方案)。
示例:使用不可变数据和函数组合。
// 函数式风格在C#中,函数式编程风格的示例可能是使用LINQ表达式来处理集合,而不是使用传统的循环:
var doubled = numbers.Select(number => number * 2);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。