C#基础12-数组集合
1、数组
(1)概念与定义
本质:C# 数组是 System.Array 的派生对象,用于存储固定长度的同类型元素集合。 声明语法
数据类型[ ] 数组名;
数据类型[ , ] 数组名;
数据类型[ ] [ ] 数组名;
关键特性
固定长度:创建后长度不可变(需动态集合请用 List<T>) 索引访问:元素从 0 开始索引(如 arr[0]) 类型安全:所有元素必须为同一类型
(2)分类与初始化
int [ ] numbers1 = new int [ 3 ] ;
int [ ] numbers2 = { 1 , 2 , 3 } ;
int [ ] numbers3 = new int [ ] { 4 , 5 , 6 } ;
int [ , ] matrix = new int [ 2 , 3 ] ;
int [ , ] matrix2 = { { 1 , 2 } , { 3 , 4 } } ;
int [ ] [ ] jagged = new int [ 3 ] [ ] ;
jagged[ 0 ] = new int [ ] { 1 } ;
jagged[ 1 ] = new int [ ] { 2 , 3 } ;
对象数组:声明后需手动初始化每个对象(否则为 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 ] ) ;
arr[ 1 ] = 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)应用场景示例
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)非泛型集合(不推荐,仅兼容旧代码)
类型 问题 替代方案 ArrayList存储 object 类型,需强制转换易出错 List<T>Hashtable键值均为 object,性能低 Dictionary<TKey, TValue>Queue非类型安全 Queue<T>Stack拆箱导致性能损失 Stack<T>
关键缺陷:
存储时值类型需装箱(如 int→object),读取时拆箱,性能差。 无编译时类型检查,易引发运行时错误(如 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#中,通过IEnumerable和IEnumerator接口实现:
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;
}
}
IEnumerable< int > TakeUntilNegative ( int [ ] numbers) {
foreach ( int num in numbers) {
if ( num < 0 ) yield break ;
yield return num;
}
}
适用场景:方法返回类型必须是IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T> 。 禁止情况
❌ 不能用于 unsafe 代码块、ref/out 参数、匿名方法。 ❌ yield return 不可置于 try-catch 中,但可放在 try-finally 的 try 块内。 ❌ 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 ( var num in GenerateSequence ( ) )
{
Console. WriteLine ( num) ;
}
0
1
2
Finally block executed! // 循环结束后执行
foreach ( var num in GenerateSequence ( ) )
{
if ( num == 1 ) break ;
Console. WriteLine ( num) ;
}
0
Finally block executed! // break 后立即执行
try
{
foreach ( var num in GenerateSequence ( ) )
{
if ( num == 1 ) throw new Exception ( "Test error" ) ;
Console. WriteLine ( num) ;
}
}
catch { }
0
Finally block executed! // 异常抛出后仍执行
(5)关键注意事项
yield return 与 try-catch 的兼容性
yield return 不能放在 try-catch 中,但可以放在 try-finally 的 try 块内。yield break 可安全用于 try 块或 catch 块,但不可用于 finally 块。 资源释放的最佳实践
在 finally 块中释放资源(如关闭文件流、数据库连接)是安全的
IEnumerable< string > ReadFile ( string path)
{
using var reader = new StreamReader ( path) ;
while ( ! reader. EndOfStream)
yield return reader. ReadLine ( ) ;
}
- `using` 语句本质是 `try-finally` 的语法糖,与 `yield` 兼容。