【C# 核心】深入剖析 C# 常用数据结构与类型:从基础到集合的精通指南

文章标题:【C# 核心】深入剖析 C# 常用数据结构与类型:从基础到集合的精通指南

标签: #CSharp #数据结构 #集合 #List #Dictionary #Array #String #.NET #编程基础 #性能优化


引言

对于任何 C# 开发者而言,深刻理解和熟练运用语言内置的数据结构与类型是构建高效、稳定和可维护应用程序的前提。这些构件不仅是存储和组织数据的基本单元,更是算法实现和系统设计的核心。本文旨在全面梳理 C# 中最常用的数据结构和类型,从基础的值类型、引用类型,到功能强大的泛型集合,结合代码示例与详尽注释,助您夯实基础,拓展视野。


一、 C# 类型系统概览

C# 的类型系统是强类型的,这意味着每个变量和常量都有一个类型,编译器会在编译时检查类型兼容性。C# 中的类型主要分为两大类:

  1. 值类型 (Value Types):

    • 变量直接包含其实际数据。
    • 通常在栈 (Stack) 上分配内存(局部变量、方法参数)。
    • 包括:
      • 简单类型(如 int, char, bool, float, double, decimal
      • 结构体 (struct)
      • 枚举 (enum)
    • 赋值时进行数据复制。
  2. 引用类型 (Reference Types):

    • 变量存储的是对数据实际位置的引用(内存地址)。
    • 数据通常在堆 (Heap) 上分配内存。
    • 包括:
      • 类 (class)
      • 接口 (interface)
      • 委托 (delegate)
      • 数组 (array)
      • 字符串 (string - 特殊的引用类型,具有值类型的某些行为,如不变性)
    • 赋值时复制的是引用,多个变量可以指向同一块内存数据。

二、 常用基础数据类型

这些是构成更复杂结构的基础。

1. 基本数值与布尔类型

  • 描述: 用于表示整数、浮点数、高精度小数以及逻辑真/假。
  • int: 32位有符号整数。
  • long: 64位有符号整数。
  • float: 单精度浮点数。
  • double: 双精度浮点数 (常用)。
  • decimal: 128位高精度十进制数,常用于金融计算,避免浮点误差。
  • bool: 布尔类型,值为 truefalse
  • 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个整数的数组,默认值为0
    • string[] 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 (引用类型)
继承不能继承其他 structclass (但可实现接口)可以继承其他 class (单继承) 和实现接口
内存分配通常在栈上 (除非是类的成员)通常在堆上
默认值成员初始化为其类型的默认值引用为 null,成员为其类型的默认值
赋值复制整个数据实例复制引用
null不能为 null (除非是 Nullable<T>)可以为 null
用途轻量级对象,数据容器,不变性有益时复杂的对象,需要继承、多态、可变状态时

结论

C# 提供了丰富且设计良好的数据结构与类型库,它们是构建任何应用程序的基础。理解每种数据结构的特性、性能影响以及适用场景,是成为一名高效 C# 开发者的关键一步。通过本文的介绍和示例,希望能帮助您更自信地选择和使用这些强大的工具,编写出更优雅、更高效的代码。持续实践和探索,您将能更深刻地体会到这些基础构件的精妙之处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PGFA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值