C#基础12-数组集合

零、文章目录

C#基础12-数组集合

1、数组

(1)概念与定义
  • 本质:C# 数组是 System.Array 的派生对象,用于存储固定长度的同类型元素集合。
  • 声明语法
数据类型[] 数组名;          // 一维数组声明
数据类型[,] 数组名;         // 二维矩形数组
数据类型[][] 数组名;        // 交错数组(数组的数组)
  • 关键特性
    • 固定长度:创建后长度不可变(需动态集合请用 List<T>
    • 索引访问:元素从 0 开始索引(如 arr[0]
    • 类型安全:所有元素必须为同一类型
(2)分类与初始化
  • 一维数组
int[] numbers1 = new int[3];           // 声明长度(元素默认为0)
int[] numbers2 = {1, 2, 3};            // 声明并初始化(隐式长度)
int[] numbers3 = new int[] {4, 5, 6};  // 显式初始化 
  • 多维矩形数组:内存连续存储,每行长度必须相同
int[,] matrix = new int[2, 3];          // 2行3列(元素默认0)
int[,] matrix2 = {{1, 2}, {3, 4}};      // 初始化二维数组
  • 交错数组:数组的数组,每行可独立定义长度
int[][] jagged = new int[3][];           // 声明3个子数组
jagged[0] = new int[] {1};              // 第一行长度1
jagged[1] = new int[] {2, 3};           // 第二行长度2 
  • 对象数组:声明后需手动初始化每个对象(否则为 null
class Student { public string Name; }
Student[] students = new Student[2];    // 声明对象数组
students[0] = new Student { Name = "Alice" }; // 需逐元素实例化!
(3)核心操作与方法
  • 元素访问与修改
int[] arr = {10, 20, 30};
Console.WriteLine(arr[1]);    // 输出20 
arr[1] = 99;                  // 修改为99 
  • 常用静态方法
方法作用示例
Array.Sort(arr)升序排序Array.Sort(numbers);
Array.Reverse(arr)反转数组Array.Reverse(names);
Array.IndexOf(arr, value)查找元素索引int pos = Array.IndexOf(arr, 5);
arr.Clone()浅拷贝数组int[] copy = (int[])arr.Clone();
  • 属性与长度控制
int len = arr.Length;          // 获取一维数组长度 
int rows = matrix.GetLength(0); // 获取多维数组行数
int cols = matrix.GetLength(1); // 获取多维数组列数
(4)关键注意事项
  • 内存分配
    • 值类型数组(如 int[])直接在栈分配连续内存
    • 引用类型数组(如 string[])存储对象引用,对象本身在堆中
  • 初始化要求
    • 元素未赋值时:数值类型为 0,布尔类型为 false,引用类型为 null
    • 对象数组需显式实例化每个元素(避免空引用异常)
  • 跨语言差异
    • C# 声明数组时方括号 [] 必须紧接类型名(如 int[] arr 而非 int arr[]
  • 优化建议
    • 频繁增删数据时优先用 List<T>
    • 大规模数值计算考虑内存连续的矩形数组提升性能
(5)应用场景示例
  • 成绩管理系统
// 定义3个学生的2门课成绩
int[,] scores = {{85, 90}, {72, 88}, {93, 79}}; 
 
// 计算第二门课平均分 
double sum = 0;
for (int i = 0; i < scores.GetLength(0); i++) {
    sum += scores[i, 1];
}
Console.WriteLine($"平均分:{sum / scores.GetLength(0):F1}");
  • 动态生成交错数组
// 根据输入生成不规则数据 
int[][] data = new int[3][]; 
for (int i = 0; i < data.Length; i++) {
    Console.Write($"第{i+1}组数据个数:");
    int size = int.Parse(Console.ReadLine());
    data[i] = new int[size];  // 动态分配每行长度
}

2、集合

(1)概念与定义
  • 在 C# 中,集合(Collection) 是用于存储、管理和操作一组相关对象的特殊数据结构,它提供了比基础数组更灵活的功能(如动态调整大小、多种访问方式等)。
  • 集合的核心特征
    • 动态容量:与固定长度的数组不同(如 int[]),集合(如 List<T>)可动态扩展或收缩,无需手动管理内存 。
    • 类型支持
      • 泛型集合(如 List<T>Dictionary<K,V>)强制类型安全,避免装箱/拆箱开销。
      • 非泛型集合(如 ArrayList)存储 object 类型,存在类型转换风险 。
    • 多样化数据结构:支持列表、字典、栈、队列等结构,适应不同场景需求 。
(2)泛型集合(推荐)
  • 命名空间:System.Collections.Generic
  • List<T>(动态数组)
    • 特点:动态扩容、索引访问、允许重复元素
    • 适用场景:需频繁增删改查的列表数据(如商品列表)
  • Dictionary<TKey, TValue>(键值对字典)
    • 特点:按键快速查找(哈希表实现)、键必须唯一
    • 适用场景:缓存、数据映射(如 ID 查用户信息)
  • Queue<T>(先进先出队列)
    • 特点:FIFO(先进先出),支持入队(Enqueue)/出队(Dequeue
    • 适用场景:任务调度、消息处理(如订单排队)
  • Stack<T>(后进先出栈)
    • 特点:LIFO(后进先出),支持压栈(Push)/弹栈(Pop
    • 适用场景:撤销操作、递归调用(如浏览器后退栈)
  • HashSet<T>(无序唯一集合)
    • 特点:元素唯一、快速去重、集合运算(交集/并集)
    • 适用场景:去重过滤、权限校验(如用户角色集合)
(3)非泛型集合(不推荐,仅兼容旧代码)
  • 命名空间:System.Collections
类型问题替代方案
ArrayList存储 object 类型,需强制转换易出错List<T>
Hashtable键值均为 object,性能低Dictionary<TKey, TValue>
Queue非类型安全Queue<T>
Stack拆箱导致性能损失Stack<T>
  • 关键缺陷:
    • 存储时值类型需装箱(如 intobject),读取时拆箱,性能差。
    • 无编译时类型检查,易引发运行时错误(如 InvalidCastException)。
(4)线程安全集合(多线程场景)
  • 命名空间:System.Collections.Concurrent
  • ConcurrentBag<T>:无序集合,适合线程间独立添加/取出元素(如日志收集)。
  • ConcurrentDictionary<TKey, TValue>:线程安全字典,高并发读写(如全局配置管理)。
  • ConcurrentQueue<T> / ConcurrentStack<T>:线程安全的队列/栈(如生产者-消费者模型)。
(5)选择指南
需求场景推荐集合理由
动态列表,频繁增删List<T>内存连续、索引高效
按键快速查找Dictionary<TKey,TValue>哈希表实现,O(1) 查找
任务队列处理Queue<T>保证先进先出顺序
去重或集合运算HashSet<T>唯一性保障、内置集合操作
高并发读写ConcurrentDictionary无锁设计,线程安全

3、迭代器

(1)核心概念
  • 迭代器(Iterator)是一种行为设计模式,提供顺序访问聚合对象(如集合)元素的方法,无需暴露其内部结构。在C#中,通过IEnumerableIEnumerator接口实现:
    • IEnumerable:标识对象可被迭代,需实现GetEnumerator()方法返回迭代器。
    • IEnumerator:定义迭代逻辑,包含:
      • Current:返回当前元素。
      • MoveNext():移动到下一元素,返回bool指示是否成功。
      • Reset():重置迭代位置(现代代码较少使用)。
  • foreach的底层原理
foreach (var item in collection) { ... }
- 编译后等效于。
IEnumerator e = collection.GetEnumerator();
while (e.MoveNext()) {
    var item = e.Current;
    ...
}
- 任何实现`IEnumerable`的类型均可被`foreach`遍历。
(2)实现方式
  • 方式1:手动实现接口(传统)
    • 需完整实现IEnumerator接口,维护迭代状态(如索引)。
    • 适用场景:需精细控制迭代逻辑(如反向遍历)。
public class GameEnumerator : IEnumerator {
    private string[] _games;
    private int _position = -1;  // 初始位置在首个元素前 
 
    public GameEnumerator(string[] games) => _games = games;
 
    public object Current => _games[_position];  // 越界检查需自行处理 
 
    public bool MoveNext() {
        _position++;
        return _position < _games.Length;
    }
    public void Reset() => _position = -1;
}
  • 方式2:使用yield return(推荐)
    • C# 2.0引入的语法糖,编译器自动生成状态机,简化代码。
    • 优势:
      • 代码简洁,无需显式实现IEnumerator
      • 支持延迟计算(Lazy Evaluation):元素在MoveNext()调用时才生成。
      • 可用yield break提前终止迭代。
public IEnumerable<int> GetNumbers() {
    yield return 1;
    yield return 2;
    yield return 3;
}
(3)关键特性
  • 延迟执行(Lazy Evaluation):迭代器仅在遍历时动态生成元素,适用于大数据集或无限序列(如斐波那契数列)。
public IEnumerable<long> Fibonacci() {
    long a = 0, b = 1;
    while (true) {
        yield return b;
        (a, b) = (b, a + b);
    }
}
  • 线程安全与状态独立:每次调用GetEnumerator()返回独立迭代器,避免多线程冲突。
  • foreach的只读约束:foreach循环中不可修改集合元素(数组除外),否则引发InvalidOperationException
  • 与LINQ集成:迭代器是LINQ查询的基石,支持链式操作。
var filtered = collection.Where(x => x > 0).Select(x => x * 2);
(4)应用场景
场景案例
自定义集合类实现IEnumerable使类支持foreach
分块处理大数据分批加载数据避免内存溢出
异步流(Async Stream)C# 8.0+ 结合await foreach处理异步序列
动态生成序列如读取大型文件时逐行返回
(5)常见误区
  • 误用Count()检查空集合:enumerable.Count() > 0会遍历全部元素,应改用enumerable.Any()
  • 忽略yield的延迟特性:包含yield的方法内代码不会立即执行,需通过迭代触发。

4、yield

(1)核心语法与作用
  • yield 通过按需生成元素和状态机机制,显著优化了迭代过程的性能与资源消耗。其核心价值在于:
    • ✅ 内存高效:适合处理海量数据或流式输入。
    • ✅ 代码简洁:避免手动实现 IEnumerator 的复杂逻辑。
    • ✅ 异步友好:无缝集成异步编程模型。
  • yield return
    • 逐个返回集合中的元素,每次调用时从上次暂停位置继续执行。
    • 示例:生成偶数序列
IEnumerable<int> GenerateEvens(int max) {
    for (int i = 0; i <= max; i += 2) {
        yield return i; // 每次循环返回一个值
    }
}
  • yield break
    • 提前终止迭代过程。
    • 示例:遇到负数时终止
IEnumerable<int> TakeUntilNegative(int[] numbers) {
    foreach (int num in numbers) {
        if (num < 0) yield break; // 终止迭代
        yield return num;
    }
}
  • 适用场景:方法返回类型必须是IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>
  • 禁止情况
    • ❌ 不能用于 unsafe 代码块、ref/out 参数、匿名方法。
    • yield return 不可置于 try-catch 中,但可放在 try-finallytry 块内。
    • yield break 不可用于 finally 块。
(2)底层原理与特性
  • 状态机实现
    • 编译器将含 yield 的方法转换为状态机类,通过 _state 字段记录执行位置,实现暂停与恢复。
  • 延迟执行(惰性求值)
    • 迭代器仅在遍历时(如 foreach 循环)动态生成元素,减少内存占用。
    • 对比:常规方法需预加载所有元素到内存。
  • 线程与执行流
    • 单线程内按需执行:yield return 返回元素后控制权交还调用方,下次请求时从断点继续。
(3)典型应用场景
  • 处理大型数据流:分批读取文件或数据库,避免内存溢出
IEnumerable<string> ReadLines(string path) {
    using (var reader = new StreamReader(path)) {
        while (!reader.EndOfStream) 
            yield return reader.ReadLine();
    }
}
  • 无限序列生成:动态生成斐波那契数列
IEnumerable<long> Fibonacci() {
    long a = 0, b = 1;
    while (true) {
        yield return a;
        (a, b) = (b, a + b);
    }
}
  • 异步迭代(C# 8.0+):使用 IAsyncEnumerable<T> 支持异步流
async IAsyncEnumerable<int> FetchDataAsync() {
    for (int i = 0; i < 10; i++) {
        await Task.Delay(100);
        yield return i;
    }
}
(4)异常执行处理
  • 核心结论
    • finally 块必然执行
      • 无论 foreach 循环是正常结束、中途 break 还是因异常退出,迭代器中的 finally 块都会执行。
      • 这是编译器为迭代器生成的 IEnumerator 接口实现中 Dispose() 方法的责任。
    • 执行时机
      • foreach 循环退出时,编译器会自动调用迭代器的 Dispose() 方法,触发 finally 块。
  • 场景1:正常完成循环
IEnumerable<int> GenerateSequence()
{
    try 
    {
        for (int i = 0; i < 3; i++)
        {
            yield return i;
        }
    }
    finally 
    {
        Console.WriteLine("Finally block executed!");
    }
}
 
// 使用 foreach 循环
foreach (var num in GenerateSequence())
{
    Console.WriteLine(num);
}
  • 场景1输出:
0
1
2
Finally block executed!  // 循环结束后执行 
  • 场景2:循环中途 break
foreach (var num in GenerateSequence())
{
    if (num == 1) break;
    Console.WriteLine(num);
}
  • 场景2输出:
0 
Finally block executed!  // break 后立即执行 
  • 场景3:循环内抛出异常
try 
{
    foreach (var num in GenerateSequence())
    {
        if (num == 1) throw new Exception("Test error");
        Console.WriteLine(num);
    }
}
catch { }
  • 场景3输出:
0
Finally block executed!  // 异常抛出后仍执行
(5)关键注意事项
  • yield returntry-catch 的兼容性
    • yield return 不能放在 try-catch 中,但可以放在 try-finallytry 块内。
    • yield break 可安全用于 try 块或 catch 块,但不可用于 finally 块。
  • 资源释放的最佳实践
    • finally 块中释放资源(如关闭文件流、数据库连接)是安全的
IEnumerable<string> ReadFile(string path)
{
    using var reader = new StreamReader(path); // 等效于 finally 中的释放 
    while (!reader.EndOfStream)
        yield return reader.ReadLine();
}
- `using` 语句本质是 `try-finally` 的语法糖,与 `yield` 兼容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李宥小哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值