C#从数组到集合的演进与最佳实践

一、 数组 (Array)

数组是C#中最基础、最原始的数据集合形式。理解它,是理解所有高级集合的起点。

1. 数组的本质:连续的内存空间

当你声明一个数组,如 int[] numbers = new int[5];,你实际上是在内存中请求了一块连续的、未被分割的 空间,其大小足以存放5个int类型的数据。

  • 连续性 (Contiguous):这是数组最重要的特性。数据肩并肩地存储在一起,就像一排有编号的停车位。
  • 固定大小 (Fixed-Size):一旦数组被创建,其大小就不能再改变。你不能让一个长度为5的数组突然容纳第6个元素。
  • 类型统一 (Homogeneous):一个数组只能存储相同类型的元素。

2. 数组的声明与使用

csharp 复制代码
// 1. 声明并初始化大小
int[] scores = new int[5]; // 在内存中分配了5个int的空间,默认值为0

// 2. 通过索引访问 (从0开始)
scores[0] = 95;
scores[1] = 88;
int firstScore = scores[0];

// 3. 声明并直接初始化内容
string[] names = new string[] { "Alice", "Bob", "Charlie" };
// 或者更简洁的语法
string[] namesShort = { "Alice", "Bob", "Charlie" };

// 4. 遍历数组
foreach (string name in names)
{
    Console.WriteLine(name);
}

// 5. 获取长度
int nameCount = names.Length; // 结果是 3

3. 数组的优缺点

优点:

  • 极高的访问性能 :由于内存是连续的,通过索引 array[i] 访问元素是一个 O(1) 操作。CPU可以直接通过 基地址 + i * 单个元素大小 的公式计算出内存地址,无需任何遍历。这使得数组在需要频繁随机读取的场景下无与伦比。
  • 内存效率高:除了数据本身,几乎没有额外的开销。

缺点:

  • 大小不可变:这是数组最大的"原罪"。在创建时必须知道所需空间,这在许多动态场景下是不现实的。
  • 插入和删除效率低下:在数组中间插入或删除一个元素,需要移动该位置之后的所有元素来填补空位或腾出空间,这是一个 O(n) 操作,非常耗时。

二、List<T> - 动态数组

1. List<T> 初识

List<T> 的内部其实就封装了一个数组。它之所以能"动态"增长,是因为它实现了一套巧妙的**容量管理(Capacity Management)**机制。

  • Capacity vs. Count :
    • Count: 列表实际包含的元素数量。
    • Capacity: 内部数组能够容纳 的元素数量。Capacity >= Count 恒成立。

当你向一个List<T>添加元素时,如果Count即将超过CapacityList<T>会自动执行以下操作:

  1. 创建一个新的、更大的数组(通常是当前容量的两倍)。
  2. 将旧数组中的所有元素复制到新数组中。
  3. 丢弃旧数组,将内部引用指向新数组。
  4. 在新数组的末尾添加新元素。
csharp 复制代码
List<int> numbers = new List<int>(); // 初始Capacity通常为0
Console.WriteLine($"Count: {numbers.Count}, Capacity: {numbers.Capacity}");

numbers.Add(1); // 添加元素
Console.WriteLine($"Count: {numbers.Count}, Capacity: {numbers.Capacity}");  // 创建Capacity为4的容器
numbers.Add(2);
numbers.Add(3);
numbers.Add(4);
numbers.Add(5); // 此时会触发内部数组的扩容和复制!此时Capacity为8

Console.WriteLine($"Count: {numbers.Count}, Capacity: {numbers.Capacity}");

2. List<T> 的使用

List<T> 提供了比数组更加丰富的API方法:

1. 添加和插入元素 (Adding and Inserting Elements)

方法 (Method) 说明 (Description) 示例 (Example) List<string> fruits = new List<string>();
Add(T item) 在列表的末尾添加一个元素。 fruits.Add("Apple"); // ["Apple"]
AddRange(IEnumerable<T> collection) 将一个集合中的所有元素添加到列表的末尾。 var moreFruits = new[] { "Banana", "Cherry" }; fruits.AddRange(moreFruits); // ["Apple", "Banana", "Cherry"]
Insert(int index, T item) 在列表的指定索引处插入一个元素。 fruits.Insert(1, "Orange"); // ["Apple", "Orange", "Banana", "Cherry"]
InsertRange(int index, IEnumerable<T> collection) 在列表的指定索引处插入一个集合的所有元素。 var tropical = new[] { "Mango", "Pineapple" }; fruits.InsertRange(2, tropical);
Contains(T item) 判断列表中是否包含指定的元素。返回 bool bool hasApple = fruits.Contains("Apple"); // true
Exists(Predicate<T> match) 判断列表中是否存在满足指定条件的元素。使用Lambda表达式。 bool hasShortName = fruits.Exists(f => f.Length < 6); // true ("Apple")
Find(Predicate<T> match) 搜索满足指定条件的第一个元素并返回它。如果找不到,返回该类型的默认值(如引用类型为null)。 string bFruit = fruits.Find(f => f.StartsWith("B")); // "Banana"
FindAll(Predicate<T> match) 检索所有满足指定条件的元素,并返回一个包含它们的新 List<T> var longNameFruits = fruits.FindAll(f => f.Length > 5); // ["Orange", "Banana"]
IndexOf(T item) 搜索指定元素,并返回其第一次出现的索引。如果找不到,返回 -1 int index = fruits.IndexOf("Banana"); // 2
LastIndexOf(T item) 搜索指定元素,并返回其最后一次出现的索引。如果找不到,返回 -1 // If fruits = ["A", "B", "A"], LastIndexOf("A") is 2
Remove(T item) 从列表中移除第一次出现的指定元素。成功移除返回 true fruits.Remove("Apple"); // ["Orange", "Banana", "Apple"]
RemoveAt(int index) 移除列表中指定索引处的元素。 fruits.RemoveAt(1); // ["Apple", "Banana", "Apple"]
RemoveAll(Predicate<T> match) 移除所有满足指定条件的元素。返回被移除的元素数量。 int removedCount = fruits.RemoveAll(f => f == "Apple"); // ["Orange", "Banana"]
RemoveRange(int index, int count) 从指定索引开始,移除指定数量的元素。 fruits.RemoveRange(1, 2); // ["Apple", "Apple"]
Clear() 从列表中移除所有元素。Count 变为 0。 fruits.Clear(); // []
Sort() 使用默认比较器对列表中的元素进行就地排序(In-place sort)。 fruits.Sort(); // ["Apple", "Banana", "Cherry"]
Sort(Comparison<T> comparison) 使用指定的委托(通常是Lambda)对元素进行就地排序 fruits.Sort((a, b) => a.Length.CompareTo(b.Length)); // Sort by length
Reverse() 将列表中的元素顺序进行就地反转 fruits.Reverse(); // ["Banana", "Apple", "Cherry"]
ForEach(Action<T> action) 对列表中的每个元素执行指定的操作。 fruits.ForEach(f => Console.WriteLine(f.ToUpper()));
ToArray() 将列表中的元素复制到一个新的数组中。 string[] fruitArray = fruits.ToArray();
GetRange(int index, int count) 创建一个新列表,其中包含源列表中从指定索引开始的指定数量的元素。 var subList = fruits.GetRange(0, 1); // New List containing ["Apple"]

三、Dictionary等特殊集合

1. Dictionary<TKey, TValue> -- 字典

当你需要通过一个唯一的"键"(Key)来快速查找一个"值"(Value)时,Dictionary是你的首选。

  • 本质:基于哈希表(Hash Table)实现。它通过一个哈希函数将Key转换成一个索引,从而实现近乎O(1)的查找、插入和删除性能。
  • 核心特性 :Key必须是唯一的,且不可为null
csharp 复制代码
Dictionary<string, int> studentAges = new Dictionary<string, int>();

// 添加键值对
studentAges.Add("Alice", 20);
studentAges["Bob"] = 22; // 更方便的索引器语法

// 查找
if (studentAges.TryGetValue("Alice", out int age))
{
    Console.WriteLine($"Alice's age is {age}"); // 推荐用法,避免异常
}

// 遍历
foreach (var pair in studentAges)
{
    Console.WriteLine($"Key: {pair.Key}, Value: {pair.Value}");
}

2. HashSet<T> - 唯一数组

当你只关心一个元素是否存在 于集合中,并且需要保证集合中没有重复元素 时,HashSet<T>是完美的选择。

  • 本质:同样基于哈希表,但只存储Key(元素本身),没有Value。
  • 核心特性:元素唯一,查找效率极高(O(1))。支持高效的集合运算(交集、并集、差集)。
csharp 复制代码
HashSet<string> uniqueNames = new HashSet<string>();
uniqueNames.Add("Alice");
uniqueNames.Add("Bob");
uniqueNames.Add("Alice"); // 添加失败,因为 "Alice" 已存在

Console.WriteLine(uniqueNames.Count); // 输出: 2

bool hasBob = uniqueNames.Contains("Bob"); // 极快

3. Queue<T>Stack<T> - 队列和堆栈

  • Queue<T> (队列)先进先出 (FIFO - First-In, First-Out)

    • Enqueue(): 入队(添加到队尾)。
    • Dequeue(): 出队(从队首移除并返回)。
  • Stack<T> (栈)后进先出 (LIFO - Last-In, First-Out)

    • Push(): 入栈(添加到栈顶)。
    • Pop(): 出栈(从栈顶移除并返回)。

结语

点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文

相关推荐
~无忧花开~3 小时前
JavaScript实现PDF本地预览技巧
开发语言·前端·javascript
小时前端3 小时前
“能说说事件循环吗?”—— 我从候选人回答中看到的浏览器与Node.js核心差异
前端·面试·浏览器
IT_陈寒3 小时前
Vite 5.0实战:10个你可能不知道的性能优化技巧与插件生态深度解析
前端·人工智能·后端
SAP庖丁解码3 小时前
【SAP Web Dispatcher负载均衡】
运维·前端·负载均衡
天蓝色的鱼鱼4 小时前
Ant Design 6.0 正式发布:前端开发者的福音与革新
前端·react.js·ant design
HIT_Weston4 小时前
38、【Ubuntu】【远程开发】拉出内网 Web 服务:构建静态网页(一)
linux·前端·ubuntu
FuckPatience4 小时前
.netcoreapp2.0与.Net Core是什么关系
c#·.net·.netcore
零一科技4 小时前
Vue3拓展:自定义权限指令
前端·vue.js
im_AMBER4 小时前
AI井字棋项目开发笔记
前端·笔记·学习·算法