文章标题:【C# 核心】深入剖析 C# 常用数据结构与类型:从基础到集合的精通指南
标签: #CSharp #数据结构 #集合 #List #Dictionary #Array #String #.NET #编程基础 #性能优化
引言
对于任何 C# 开发者而言,深刻理解和熟练运用语言内置的数据结构与类型是构建高效、稳定和可维护应用程序的前提。这些构件不仅是存储和组织数据的基本单元,更是算法实现和系统设计的核心。本文旨在全面梳理 C# 中最常用的数据结构和类型,从基础的值类型、引用类型,到功能强大的泛型集合,结合代码示例与详尽注释,助您夯实基础,拓展视野。
一、 C# 类型系统概览
C# 的类型系统是强类型的,这意味着每个变量和常量都有一个类型,编译器会在编译时检查类型兼容性。C# 中的类型主要分为两大类:
-
值类型 (Value Types):
- 变量直接包含其实际数据。
- 通常在栈 (Stack) 上分配内存(局部变量、方法参数)。
- 包括:
- 简单类型(如
int
,char
,bool
,float
,double
,decimal
) - 结构体 (
struct
) - 枚举 (
enum
)
- 简单类型(如
- 赋值时进行数据复制。
-
引用类型 (Reference Types):
- 变量存储的是对数据实际位置的引用(内存地址)。
- 数据通常在堆 (Heap) 上分配内存。
- 包括:
- 类 (
class
) - 接口 (
interface
) - 委托 (
delegate
) - 数组 (
array
) - 字符串 (
string
- 特殊的引用类型,具有值类型的某些行为,如不变性)
- 类 (
- 赋值时复制的是引用,多个变量可以指向同一块内存数据。
二、 常用基础数据类型
这些是构成更复杂结构的基础。
1. 基本数值与布尔类型
- 描述: 用于表示整数、浮点数、高精度小数以及逻辑真/假。
int
: 32位有符号整数。long
: 64位有符号整数。float
: 单精度浮点数。double
: 双精度浮点数 (常用)。decimal
: 128位高精度十进制数,常用于金融计算,避免浮点误差。bool
: 布尔类型,值为true
或false
。char
: 16位 Unicode 字符。
using System;
public class BasicTypesExample
{
public static void Demonstrate()
{
// --- int ---
// 输入: 直接赋值整数
int age = 30;
// 输出: 30
Console.WriteLine($"Age (int): {age}");
// --- double ---
// 输入: 直接赋值浮点数
double piApproximation = 3.14159;
// 输出: 3.14159
Console.WriteLine($"Pi Approximation (double): {piApproximation}");
// --- decimal ---
// 输入: 直接赋值高精度小数,注意 'm' 后缀
decimal accountBalance = 12345.67m;
// 输出: 12345.67
Console.WriteLine($"Account Balance (decimal): {accountBalance}");
// --- bool ---
// 输入: 直接赋值 true 或 false
bool isActive = true;
// 输出: True
Console.WriteLine($"Is Active (bool): {isActive}");
// --- char ---
// 输入: 直接赋值单个字符,使用单引号
char initial = 'J';
// 输出: J
Console.WriteLine($"Initial (char): {initial}");
}
}
2. string
(字符串)
- 描述: 表示一系列 Unicode 字符。在 C# 中,
string
是一个引用类型,但它表现出某些值类型的特征,最重要的是其不变性 (immutability)。这意味着一旦字符串被创建,其内容就不能被更改。任何看似修改字符串的操作(如连接、替换)实际上都会创建一个新的字符串对象。 - 常用操作: 连接 (
+
或string.Concat
)、获取长度 (Length
)、索引访问、子串 (Substring
)、比较 (Equals
,==
)、查找 (IndexOf
,Contains
)、替换 (Replace
)、大小写转换 (ToUpper
,ToLower
)、分割 (Split
)、格式化 (string.Format
或插值字符串$"..."
)。 - 性能考量: 由于其不变性,大量频繁的字符串拼接操作(尤其是在循环中)应使用
StringBuilder
类以提高性能。
using System;
using System.Text; // For StringBuilder
public class StringExample
{
public static void Demonstrate()
{
// --- 字符串创建与连接 ---
// 输入: "Hello" 和 "World"
string greeting = "Hello";
string name = "World";
string message = greeting + ", " + name + "!"; // 创建新字符串
// 输出: Hello, World!
Console.WriteLine($"Message: {message}");
// 输出: 13 (字符串长度)
Console.WriteLine($"Message length: {message.Length}");
// --- 字符串常用方法 ---
// 输入: message = "Hello, World!"
string upperMessage = message.ToUpper();
// 输出: HELLO, WORLD!
Console.WriteLine($"Uppercase: {upperMessage}");
bool containsWorld = message.Contains("World");
// 输入: 查找 "World"
// 输出: True
Console.WriteLine($"Contains 'World': {containsWorld}");
string replacedMessage = message.Replace("World", "C#");
// 输入: 将 "World" 替换为 "C#"
// 输出: Hello, C#!
Console.WriteLine($"Replaced: {replacedMessage}");
// --- StringBuilder for efficient string manipulation ---
// 场景: 需要在循环中拼接大量字符串
StringBuilder sb = new StringBuilder();
// 输入: 循环添加数字
for (int i = 0; i < 5; i++)
{
sb.Append(i).Append("-"); // Append 返回 StringBuilder 自身,支持链式调用
}
// 移除最后一个多余的 "-"
if (sb.Length > 0) sb.Length--; // 这是一个小技巧,直接修改长度
// 输出: 0-1-2-3-4
Console.WriteLine($"StringBuilder result: {sb.ToString()}");
}
}
三、 常用集合类型 (主要来自 System.Collections.Generic
)
泛型集合提供了类型安全,避免了早期非泛型集合所需的装箱/拆箱操作,性能更优。
1. Array
(数组 T[]
)
- 描述: 固定大小的、相同类型元素的有序集合。数组在内存中是连续存储的,这使得通过索引访问元素非常快 (O(1))。一旦创建,数组的大小就不能改变。
- 特性:
- 强类型。
- 大小固定。
- 零基索引 (从 0 开始)。
- 实现了
IEnumerable<T>
,支持 LINQ 查询。
- 声明与初始化:
int[] numbers = new int[5];
// 声明一个包含5个整数的数组,默认值为0string[] names = new string[] { "Alice", "Bob", "Charlie" };
// 声明并初始化char[] vowels = { 'a', 'e', 'i', 'o', 'u' };
// 简化初始化
using System;
public class ArrayExample
{
public static void Demonstrate()
{
// --- 数组声明与初始化 ---
// 输入: 预定义的一组整数 { 10, 20, 30, 40, 50 }
int[] scores = new int[] { 10, 20, 30, 40, 50 };
// --- 访问数组元素 (通过索引) ---
// 输入: 索引 2
int thirdScore = scores[2]; // 访问第三个元素 (索引从0开始)
// 输出: 30
Console.WriteLine($"Third score: {thirdScore}");
// --- 修改数组元素 ---
// 输入: 索引 0, 新值 15
scores[0] = 15;
// 输出: First score (modified): 15
Console.WriteLine($"First score (modified): {scores[0]}");
// --- 遍历数组 ---
Console.WriteLine("All scores:");
// 输入: scores 数组 { 15, 20, 30, 40, 50 }
foreach (int score in scores)
{
// 输出 (每行一个):
// 15
// 20
// 30
// 40
// 50
Console.WriteLine(score);
}
// --- 数组长度 ---
// 输出: Array length: 5
Console.WriteLine($"Array length: {scores.Length}");
// --- 多维数组 (示例: 2x3 矩阵) ---
// 输入: 预定义的二维整数数组
int[,] matrix = new int[,]
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// 输入: 访问 matrix[1, 1] (第二行第二列)
// 输出: Element at [1,1]: 5
Console.WriteLine($"Element at [1,1]: {matrix[1, 1]}");
}
}
2. List<T>
(列表)
- 描述: 动态大小的、相同类型元素的有序集合。
List<T>
是对数组的封装,它可以在需要时自动调整内部数组的大小。提供了比数组更丰富的操作方法。 - 特性:
- 动态大小(内部通过数组实现,容量不足时会重新分配更大数组并复制元素)。
- 通过索引快速访问 (O(1))。
- 在列表末尾添加/删除元素效率较高 (平均 O(1),最坏 O(n) 当需要扩容时)。
- 在列表中间插入/删除元素效率较低 (O(n)),因为需要移动后续元素。
- 常用方法:
Add()
,Remove()
,RemoveAt()
,Insert()
,Contains()
,IndexOf()
,Sort()
,Clear()
,Count
(属性)。
using System;
using System.Collections.Generic;
public class ListExample
{
public static void Demonstrate()
{
// --- List 创建与添加元素 ---
// 输入: (空列表)
List<string> fruits = new List<string>();
// 输入: "Apple", "Banana", "Cherry" 依次添加
fruits.Add("Apple");
fruits.Add("Banana");
fruits.Add("Cherry");
// 此时 fruits 内容: ["Apple", "Banana", "Cherry"]
// --- 访问 List 元素 (通过索引) ---
// 输入: 索引 1
string secondFruit = fruits[1];
// 输出: Second fruit: Banana
Console.WriteLine($"Second fruit: {secondFruit}");
// --- 遍历 List ---
Console.WriteLine("All fruits:");
// 输入: fruits 列表 ["Apple", "Banana", "Cherry"]
foreach (string fruit in fruits)
{
// 输出 (每行一个):
// Apple
// Banana
// Cherry
Console.WriteLine(fruit);
}
// --- 插入元素 ---
// 输入: 索引 1, 元素 "Blueberry"
fruits.Insert(1, "Blueberry"); // 在 "Banana" 前插入 "Blueberry"
// 此时 fruits 内容: ["Apple", "Blueberry", "Banana", "Cherry"]
// 输出: Fruits after insert: Apple, Blueberry, Banana, Cherry
Console.WriteLine($"Fruits after insert: {string.Join(", ", fruits)}");
// --- 删除元素 ---
// 输入: 元素 "Apple"
fruits.Remove("Apple"); // 按值删除第一个匹配的
// 此时 fruits 内容: ["Blueberry", "Banana", "Cherry"]
// 输出: Fruits after removing 'Apple': Blueberry, Banana, Cherry
Console.WriteLine($"Fruits after removing 'Apple': {string.Join(", ", fruits)}");
// 输入: 索引 0
fruits.RemoveAt(0); // 按索引删除
// 此时 fruits 内容: ["Banana", "Cherry"]
// 输出: Fruits after RemoveAt(0): Banana, Cherry
Console.WriteLine($"Fruits after RemoveAt(0): {string.Join(", ", fruits)}");
// --- 检查元素是否存在 ---
// 输入: "Banana"
bool hasBanana = fruits.Contains("Banana");
// 输出: Contains 'Banana': True
Console.WriteLine($"Contains 'Banana': {hasBanana}");
// --- List 大小 ---
// 输出: Number of fruits: 2
Console.WriteLine($"Number of fruits: {fruits.Count}");
}
}
3. Dictionary<TKey, TValue>
(字典)
- 描述: 键值对 (Key-Value Pair) 的集合。每个键必须是唯一的,并且用于快速检索关联的值。
Dictionary<TKey, TValue>
内部通常使用哈希表 (Hash Table) 实现。 - 特性:
- 通过键进行极快的添加、删除和查找操作 (平均 O(1))。
- 键不能为
null
(除非TKey
是可空类型),值可以为null
(如果TValue
是引用类型或可空值类型)。 - 键的类型必须正确实现
GetHashCode()
和Equals()
方法,或者在创建字典时提供一个IEqualityComparer<TKey>
。
- 常用操作:
Add(key, value)
,Remove(key)
,ContainsKey(key)
,TryGetValue(key, out value)
,this[key]
(索引器,如果键不存在会抛异常),Keys
(属性,获取所有键的集合),Values
(属性,获取所有值的集合)。
using System;
using System.Collections.Generic;
public class DictionaryExample
{
public static void Demonstrate()
{
// --- Dictionary 创建与添加键值对 ---
// 输入: (空字典)
Dictionary<int, string> studentGrades = new Dictionary<int, string>();
// 输入: (101, "Alice"), (102, "Bob"), (103, "Charlie") 依次添加
studentGrades.Add(101, "Alice"); // Key: student ID, Value: student name
studentGrades.Add(102, "Bob");
studentGrades.Add(103, "Charlie");
// 此时 studentGrades 内容: { 101: "Alice", 102: "Bob", 103: "Charlie" }
// --- 通过键访问值 (使用索引器) ---
// 输入: 键 102
string studentName = studentGrades[102]; // 如果键不存在,会抛出 KeyNotFoundException
// 输出: Student with ID 102: Bob
Console.WriteLine($"Student with ID 102: {studentName}");
// --- 安全地访问值 (使用 TryGetValue) ---
// 输入: 键 104 (不存在)
if (studentGrades.TryGetValue(104, out string foundName))
{
// 如果键存在,foundName 会被赋值,并进入此代码块
Console.WriteLine($"Student with ID 104: {foundName}");
}
else
{
// 输出: Student with ID 104 not found.
Console.WriteLine("Student with ID 104 not found.");
}
// --- 检查键是否存在 ---
// 输入: 键 101
bool hasKey101 = studentGrades.ContainsKey(101);
// 输出: Contains key 101: True
Console.WriteLine($"Contains key 101: {hasKey101}");
// --- 修改值 ---
// 输入: 键 101, 新值 "Alicia"
studentGrades[101] = "Alicia"; // 如果键已存在,则更新其值
// 输出: Updated student 101: Alicia
Console.WriteLine($"Updated student 101: {studentGrades[101]}");
// --- 删除键值对 ---
// 输入: 键 103
studentGrades.Remove(103);
// 此时 studentGrades 内容: { 101: "Alicia", 102: "Bob" }
// 输出: Contains key 103 after removal: False
Console.WriteLine($"Contains key 103 after removal: {studentGrades.ContainsKey(103)}");
// --- 遍历 Dictionary ---
Console.WriteLine("All student grades:");
// 输入: studentGrades 字典 { 101: "Alicia", 102: "Bob" }
foreach (KeyValuePair<int, string> entry in studentGrades)
{
// 输出 (每行一个, 顺序不保证):
// Key: 101, Value: Alicia
// Key: 102, Value: Bob
Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
}
// --- 获取所有键或所有值 ---
// 输出 (顺序不保证): Keys: 101, 102
Console.WriteLine($"Keys: {string.Join(", ", studentGrades.Keys)}");
// 输出 (顺序不保证): Values: Alicia, Bob
Console.WriteLine($"Values: {string.Join(", ", studentGrades.Values)}");
// --- 字典大小 ---
// 输出: Number of entries: 2
Console.WriteLine($"Number of entries: {studentGrades.Count}");
}
}
4. HashSet<T>
(哈希集)
- 描述: 包含唯一元素的无序集合。它主要用于快速检查一个元素是否存在于集合中,以及执行高效的集合运算(如并集、交集、差集)。内部也是基于哈希表实现。
- 特性:
- 元素唯一,不允许重复。
- 添加、删除、查找元素非常快 (平均 O(1))。
- 无序(元素没有特定的顺序)。
- 元素的类型必须正确实现
GetHashCode()
和Equals()
方法,或提供IEqualityComparer<T>
。
- 常用方法:
Add()
,Remove()
,Contains()
,UnionWith()
,IntersectWith()
,ExceptWith()
,IsSubsetOf()
,IsSupersetOf()
.
using System;
using System.Collections.Generic;
using System.Linq; // For easy printing
public class HashSetExample
{
public static void Demonstrate()
{
// --- HashSet 创建与添加元素 ---
// 输入: (空哈希集)
HashSet<int> uniqueNumbers = new HashSet<int>();
// 输入: 10, 20, 10 (重复), 30 依次添加
bool added10 = uniqueNumbers.Add(10); // true, 10 is added
bool added20 = uniqueNumbers.Add(20); // true, 20 is added
bool addedDuplicate10 = uniqueNumbers.Add(10); // false, 10 is already present
uniqueNumbers.Add(30);
// 此时 uniqueNumbers 内容 (无序): {10, 20, 30}
// 输出: Added 10: True, Added 20: True, Added duplicate 10: False
Console.WriteLine($"Added 10: {added10}, Added 20: {added20}, Added duplicate 10: {addedDuplicate10}");
// 输出 (顺序不保证): Unique numbers: 10, 20, 30 (or other order)
Console.WriteLine($"Unique numbers: {string.Join(", ", uniqueNumbers.OrderBy(x => x))}"); // OrderBy for consistent print
// --- 检查元素是否存在 ---
// 输入: 20
bool has20 = uniqueNumbers.Contains(20);
// 输出: Contains 20: True
Console.WriteLine($"Contains 20: {has20}");
// 输入: 40
bool has40 = uniqueNumbers.Contains(40);
// 输出: Contains 40: False
Console.WriteLine($"Contains 40: {has40}");
// --- 删除元素 ---
// 输入: 20
uniqueNumbers.Remove(20);
// 此时 uniqueNumbers 内容 (无序): {10, 30}
// 输出: Contains 20 after removal: False
Console.WriteLine($"Contains 20 after removal: {uniqueNumbers.Contains(20)}");
// --- 集合运算 ---
// 输入: setA = {1, 2, 3}, setB = {3, 4, 5}
HashSet<int> setA = new HashSet<int> { 1, 2, 3 };
HashSet<int> setB = new HashSet<int> { 3, 4, 5 };
// 并集 (Union)
HashSet<int> unionSet = new HashSet<int>(setA); // Copy setA
unionSet.UnionWith(setB); // Modifies unionSet
// 输出 (顺序不保证): Union: 1, 2, 3, 4, 5
Console.WriteLine($"Union: {string.Join(", ", unionSet.OrderBy(x => x))}");
// 交集 (Intersection)
HashSet<int> intersectSet = new HashSet<int>(setA);
intersectSet.IntersectWith(setB);
// 输出 (顺序不保证): Intersection: 3
Console.WriteLine($"Intersection: {string.Join(", ", intersectSet.OrderBy(x => x))}");
// 差集 (Except) A - B
HashSet<int> exceptSet = new HashSet<int>(setA);
exceptSet.ExceptWith(setB);
// 输出 (顺序不保证): A except B: 1, 2
Console.WriteLine($"A except B: {string.Join(", ", exceptSet.OrderBy(x => x))}");
// --- HashSet 大小 ---
// 输出: Number of unique numbers: 2 (after removing 20)
Console.WriteLine($"Number of unique numbers: {uniqueNumbers.Count}");
}
}
5. Queue<T>
(队列)
- 描述: 先进先出 (FIFO - First-In, First-Out) 的集合。元素从队尾加入,从队头移除。
- 应用场景: 任务调度、消息队列、广度优先搜索 (BFS) 等。
- 常用方法:
Enqueue(item)
(入队),Dequeue()
(出队并返回元素,队列为空抛异常),Peek()
(查看队头元素但不移除,队列为空抛异常),TryDequeue(out item)
,TryPeek(out item)
.
using System;
using System.Collections.Generic;
public class QueueExample
{
public static void Demonstrate()
{
// --- Queue 创建与入队 ---
// 输入: (空队列)
Queue<string> taskQueue = new Queue<string>();
// 输入: "Task A", "Task B", "Task C" 依次入队
taskQueue.Enqueue("Task A"); // "Task A" is at the front
taskQueue.Enqueue("Task B");
taskQueue.Enqueue("Task C"); // "Task C" is at the back
// 此时 taskQueue 内容 (从头到尾): ["Task A", "Task B", "Task C"]
// --- 查看队头元素 (不移除) ---
// 输入: (当前队列)
string nextTask = taskQueue.Peek();
// 输出: Next task (Peek): Task A
Console.WriteLine($"Next task (Peek): {nextTask}");
// 输出: Queue count after Peek: 3
Console.WriteLine($"Queue count after Peek: {taskQueue.Count}"); // Count remains 3
// --- 出队 (移除并返回队头元素) ---
// 输入: (当前队列)
string_processedTask = taskQueue.Dequeue();
// 输出: Processed task (Dequeue): Task A
Console.WriteLine($"Processed task (Dequeue): {processedTask}");
// 此时 taskQueue 内容: ["Task B", "Task C"]
// 输出: Queue count after Dequeue: 2
Console.WriteLine($"Queue count after Dequeue: {taskQueue.Count}");
// --- 遍历队列 (不改变队列) ---
Console.WriteLine("Remaining tasks in queue (iteration does not dequeue):");
// 输入: taskQueue 队列 ["Task B", "Task C"]
foreach (string task in taskQueue)
{
// 输出 (按 FIFO 顺序):
// Task B
// Task C
Console.WriteLine(task);
}
// --- 安全地出队和查看 (使用 TryDequeue/TryPeek) ---
// 输入: (当前队列)
if (taskQueue.TryDequeue(out string anotherTask))
{
// 输出: Successfully dequeued: Task B
Console.WriteLine($"Successfully dequeued: {anotherTask}");
}
// 此时 taskQueue 内容: ["Task C"]
if (taskQueue.TryPeek(out string peekedTask))
{
// 输出: Next task via TryPeek: Task C
Console.WriteLine($"Next task via TryPeek: {peekedTask}");
}
// --- 队列大小 ---
// 输出: Current queue size: 1
Console.WriteLine($"Current queue size: {taskQueue.Count}");
}
}
6. Stack<T>
(栈)
- 描述: 后进先出 (LIFO - Last-In, First-Out) 的集合。元素从栈顶压入,也从栈顶弹出。
- 应用场景: 方法调用栈、撤销/重做功能、表达式求值、深度优先搜索 (DFS) 等。
- 常用方法:
Push(item)
(入栈),Pop()
(出栈并返回元素,栈为空抛异常),Peek()
(查看栈顶元素但不移除,栈为空抛异常),TryPop(out item)
,TryPeek(out item)
.
using System;
using System.Collections.Generic;
public class StackExample
{
public static void Demonstrate()
{
// --- Stack 创建与入栈 ---
// 输入: (空栈)
Stack<string> browserHistory = new Stack<string>();
// 输入: "Page1.html", "Page2.html", "Page3.html" 依次入栈
browserHistory.Push("Page1.html");
browserHistory.Push("Page2.html");
browserHistory.Push("Page3.html"); // "Page3.html" is at the top
// 此时 browserHistory 内容 (从顶到底): ["Page3.html", "Page2.html", "Page1.html"]
// --- 查看栈顶元素 (不移除) ---
// 输入: (当前栈)
string currentPage = browserHistory.Peek();
// 输出: Current page (Peek): Page3.html
Console.WriteLine($"Current page (Peek): {currentPage}");
// 输出: Stack count after Peek: 3
Console.WriteLine($"Stack count after Peek: {browserHistory.Count}"); // Count remains 3
// --- 出栈 (移除并返回栈顶元素) ---
// 输入: (当前栈)
string previousPage = browserHistory.Pop();
// 输出: Previous page (Pop): Page3.html
Console.WriteLine($"Previous page (Pop): {previousPage}");
// 此时 browserHistory 内容: ["Page2.html", "Page1.html"]
// 输出: Stack count after Pop: 2
Console.WriteLine($"Stack count after Pop: {browserHistory.Count}");
// --- 遍历栈 (不改变栈) ---
Console.WriteLine("Remaining pages in history (iteration does not pop):");
// 输入: browserHistory 栈 ["Page2.html", "Page1.html"]
foreach (string page in browserHistory) // Iterates from top to bottom
{
// 输出 (按 LIFO 顺序, 即从顶到底):
// Page2.html
// Page1.html
Console.WriteLine(page);
}
// --- 安全地出栈和查看 (使用 TryPop/TryPeek) ---
// 输入: (当前栈)
if (browserHistory.TryPop(out string anotherPage))
{
// 输出: Successfully popped: Page2.html
Console.WriteLine($"Successfully popped: {anotherPage}");
}
// 此时 browserHistory 内容: ["Page1.html"]
if (browserHistory.TryPeek(out string peekedPage))
{
// 输出: Current page via TryPeek: Page1.html
Console.WriteLine($"Current page via TryPeek: {peekedPage}");
}
// --- 栈大小 ---
// 输出: Current stack size: 1
Console.WriteLine($"Current stack size: {browserHistory.Count}");
}
}
7. LinkedList<T>
(链表)
- 描述: 双向链表。每个元素 (节点
LinkedListNode<T>
) 包含数据以及指向前一个和后一个节点的引用。 - 特性:
- 在已知节点的情况下,插入和删除元素非常快 (O(1)),因为只需要修改相邻节点的引用。
- 通过索引访问元素较慢 (O(n)),因为需要从头或尾开始遍历。
- 内存非连续分配。
- 常用方法:
AddFirst()
,AddLast()
,AddBefore(node, value)
,AddAfter(node, value)
,RemoveFirst()
,RemoveLast()
,Remove(node)
,Find(value)
(返回第一个匹配的节点)。
using System;
using System.Collections.Generic;
public class LinkedListExample
{
public static void Demonstrate()
{
// --- LinkedList 创建与添加元素 ---
// 输入: (空链表)
LinkedList<string> playlist = new LinkedList<string>();
// 输入: "Song A", "Song C", "Song B" (注意添加顺序和位置)
LinkedListNode<string> nodeA = playlist.AddLast("Song A");
LinkedListNode<string> nodeC = playlist.AddLast("Song C");
playlist.AddBefore(nodeC, "Song B"); // 在 "Song C" 前插入 "Song B"
playlist.AddFirst("Intro Tune");
// 此时 playlist 内容 (从头到尾): ["Intro Tune", "Song A", "Song B", "Song C"]
// --- 遍历 LinkedList ---
Console.WriteLine("Current playlist:");
// 输入: playlist 链表 ["Intro Tune", "Song A", "Song B", "Song C"]
foreach (string song in playlist)
{
// 输出 (按顺序):
// Intro Tune
// Song A
// Song B
// Song C
Console.WriteLine(song);
}
// --- 使用 LinkedListNode 进行操作 ---
LinkedListNode<string> currentSongNode = playlist.Find("Song B");
if (currentSongNode != null)
{
// 输入: 找到 "Song B" 节点后,在其后插入 "Interlude"
playlist.AddAfter(currentSongNode, "Interlude");
// 输出: Next song after 'Song B': Interlude (or Song C if AddAfter fails or not called)
Console.WriteLine($"Next song after '{currentSongNode.Value}': {currentSongNode.Next?.Value ?? "None"}");
// 此时 playlist 内容: ["Intro Tune", "Song A", "Song B", "Interlude", "Song C"]
}
// --- 删除元素 ---
// 输入: "Song A"
playlist.Remove("Song A"); // 按值删除第一个匹配的
// 此时 playlist 内容: ["Intro Tune", "Song B", "Interlude", "Song C"]
// 输出: Playlist after removing 'Song A': Intro Tune, Song B, Interlude, Song C
Console.WriteLine($"Playlist after removing 'Song A': {string.Join(", ", playlist)}");
playlist.RemoveFirst(); // 删除 "Intro Tune"
// 此时 playlist 内容: ["Song B", "Interlude", "Song C"]
// 输出: Playlist after RemoveFirst: Song B, Interlude, Song C
Console.WriteLine($"Playlist after RemoveFirst: {string.Join(", ", playlist)}");
// --- LinkedList 大小 ---
// 输出: Number of songs in playlist: 3
Console.WriteLine($"Number of songs in playlist: {playlist.Count}");
}
}
四、 选择合适的数据结构
选择正确的数据结构对于程序的性能和可维护性至关重要。以下是一些通用准则:
- 需要固定大小且通过索引快速访问? ->
Array (T[])
- 需要动态大小、有序列表,且常在末尾操作或按索引访问? ->
List<T>
- 需要通过唯一键快速查找、插入、删除值? ->
Dictionary<TKey, TValue>
- 需要存储唯一元素,并进行快速存在性检查或集合运算? ->
HashSet<T>
- 需要 FIFO (先进先出) 行为? ->
Queue<T>
- 需要 LIFO (后进先出) 行为? ->
Stack<T>
- 需要在集合中间频繁插入或删除元素,并且不常通过索引访问? ->
LinkedList<T>
- 需要线程安全的集合操作? -> 查看
System.Collections.Concurrent
命名空间下的集合 (如ConcurrentDictionary<TKey, TValue>
,ConcurrentQueue<T>
)。 - UI 元素需要数据绑定并响应集合变化? ->
System.Collections.ObjectModel.ObservableCollection<T>
五、 结构体 (struct
) vs 类 (class
)
简要回顾一下这两者的关键区别,因为它们影响数据如何存储和传递:
特性 | struct (值类型) | class (引用类型) |
---|---|---|
继承 | 不能继承其他 struct 或 class (但可实现接口) | 可以继承其他 class (单继承) 和实现接口 |
内存分配 | 通常在栈上 (除非是类的成员) | 通常在堆上 |
默认值 | 成员初始化为其类型的默认值 | 引用为 null ,成员为其类型的默认值 |
赋值 | 复制整个数据实例 | 复制引用 |
null | 不能为 null (除非是 Nullable<T> ) | 可以为 null |
用途 | 轻量级对象,数据容器,不变性有益时 | 复杂的对象,需要继承、多态、可变状态时 |
结论
C# 提供了丰富且设计良好的数据结构与类型库,它们是构建任何应用程序的基础。理解每种数据结构的特性、性能影响以及适用场景,是成为一名高效 C# 开发者的关键一步。通过本文的介绍和示例,希望能帮助您更自信地选择和使用这些强大的工具,编写出更优雅、更高效的代码。持续实践和探索,您将能更深刻地体会到这些基础构件的精妙之处。