本文主要讨论:List(列表),Dictionary(字典) ,Queue(队列) ,Stack(栈),HashSet(唯一集合),LinkedList(双向链表),BitArray(位数组),SortedSet(排序集合),ObservableCollection(可观察到的集合(机翻),我喜欢称为可观集合 ), Queue(队列),ConcurrentDictionary<K, V>(多线程字典(机翻是并发字典 )),Immutable Collections( 不可变的集合),Custom Collections(自定义集合)。
什么是C# 集合数据结构?Array呢?
从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:
特点:动态大小的列表,可存储任何类型的元素,支持索引访问。
适用于:动态数组,用于存储和管理元素列表。
List<int> numbers = new List<int>();
// 添加元素
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
Dictionary<K, V>:
特点:键值对存储结构,快速查找和存储数据,键唯一。
适用于:存储和管理键值对,例如配置、映射、缓存。
在Dictionary<string, int> dictionary = new Dictionary<string, int>();
dictionary.Add("apple", 1);
dictionary.Add("banana", 2);
Queue 和 Stack:
特点:队列和栈数据结构,支持先进先出 (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);
}
}
}
LinkedList:
特点:双向链表,用于频繁插入和删除操作,不适用于随机访问。
适用于:频繁插入和删除的场景,如某些算法。
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);
}
}
}
BitArray:
特点:位数组,用于处理位级别的操作。
适用于:处理大量布尔值的情况,如位图索引。
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:
无序集合,用于存储不重复的元素,基于哈希表实现。
适用场景:需要快速查找、添加、删除元素,且元素
确保元素的唯一性,适用于去重操作。
HashSet<int> hashSet = new HashSet<int>();
hashSet.Add(1);
hashSet.Add(2);
hashSet.Add(3);
SortedSet:
特点:有序集合,自动排序元素。
适用于:需要元素有序的场景。
SortedSet<int> sortedSet = new SortedSet<int>();
sortedSet.Add(3);
sortedSet.Add(1);
sortedSet.Add(2);
ObservableCollection:
特点:可观察的集合,用于绑定到界面元素并在数据更改时通知
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无返回事件:
特点:存储委托或函数,可用于实现事件队列。
适用于:处理事件队列,例如动画序列。
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>:
特点:多线程安全的字典,支持并发操作。
适用于:多线程环境下的键值对存储和操作。
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"]);
}
}
Immutable Collections:
特点:不可变的集合,一旦创建就无法修改,线程安全。
适用于:需要保持不变性的情况,函数式编程。
不可变集合是 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);
}
}
}
Custom Collections:
灵活性:可以根据需求定制集合的行为。
功能定制:可以实现特定的数据结构和算法。
适用场景
特殊需求:标准库中的集合不满足特定需求时。
优化性能:针对特定场景优化性能。
///假设我们要实现一个简单的自定义循环队列:
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);