C# ~ 从 IEnumerable / IEnumerator 到 IEnumerable<T> / IEnumerator<T> 到 yield

本文介绍了C#中枚举的相关概念,包括IEnumerable和IEnumerator接口及其泛型版本IEnumerable<T>和IEnumerator<T>。讨论了非泛型版本的不足和泛型版本的优势,并展示了如何通过自定义枚举数类实现foreach遍历。此外,还探讨了迭代器的使用,特别是yield关键字在延迟加载和控制流程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IEnumerable / IEnumerator

首先,IEnumerable / IEnumerator 接口定义如下:

public interface IEnumerable  /// 可枚举接口
{
   
   
    IEnumerator GetEnumerator();
} 
public interface IEnumerator  /// 枚举器接口
{
   
   
    object Current { get; }  
    bool MoveNext();
    void Reset();
}

:Current 没有 set 方法,在 foreach 中不能修改元素 var item 的值。
· IEnumerable:声明式的接口,声明实现了该接口的类是可枚举类型;
· IEnumerator:实现式的接口,IEnumerator 对象说明如何实现一个枚举器;

通过继承 IEnumerable / IEnumerator 接口实现自定义类使用 foreach 语句来遍历自身元素。逻辑关系图:
   精减的团队模型 - R模型

   People <-> MyClass 实现 IEnumerable 接口的 GetEnumerator()方法
   EnumeratorPeople <-> MyEnumerator 实现 IEnumerator 接口

·  定义Person类

 public class Person
 {
   private string name;
   private int age;

   public Person(string _name, int _age){
      this.name = _name, this.age = _age;
   }
   public override string ToString(){
      return string.Format("Name:{0},Age:{1}.", name, age);
   }
 }

  数组定义见主函数,以下2种遍历数组的方法等同,因为所有数组的基类都是 System.Array ,System.Array 类实现了 IEnumerable 接口,可以直接通过 GetEnumerator() 方法返回枚举数对象,枚举数对象可以依次返回请求的数组的元素。

   // 利用 foreach 遍历数组
   foreach (var person in persons)
        Console.WriteLine(person.ToString());
   // 利用 IEnumerable ~ IEnumerator 遍历数组    
   IEnumerator it = persons.GetEnumerator();
   while (it.MoveNext()){
        Person obj = (Person)(it.Current);  // 需强制类型转换
        Console.WriteLine(obj.ToString());
   }

·  定义People类 (MyClass)

  public class People 
  {
   
   
        private Person[] persons;
        public People(Person[] _persons){ 
            persons = new Person[_persons.Length];
            for (int i = 0; i < _persons.Length; ++i){
                 persons[i] = _persons[i];    
            }
        }
  }

 注意,People 类的 persons 数组是 private 变量,在主测函数中是无法遍历访问的。
 方法 1:将 private 更改为 public:

    foreach (var person in people.persons)
        Console.WriteLine(person.ToString());

 方法 2:People 类继承 IEnumerable 接口并实现 GetEnumerator() 方法,有 2 种方法:
  [-1-]. 利用数组默认实现了 IEnumerable 和 IEnumerator 接口的事实,重新定义 People 类:

  public class People : IEnumerable
  {
   
   
        ... ...
        public IEnumerator Get
<think>好的,我现在需要回答用户关于C#IEnumerable接口的作用、用法和场景的问题。首先,我应该回忆一下自己对IEnumerable的理解。IEnumerable是.NET中的一个基础接口,主要用于集合的遍历。它定义了一个GetEnumerator方法,返回IEnumerator对象,这样就可以用foreach循环来迭代集合中的元素。 接下来,作用方面,IEnumerable的主要作用是提供一种统一的遍历集合元素的方式,无论集合的具体类型是什么,比如数组、列表或者自定义的集合类。这样一来,开发者不需要关心集合内部的结构,只需要通过foreach来访问每个元素。 然后是用法。用户可能想知道如何在代码中实现IEnumerable接口。比如,创建一个自定义集合类,实现GetEnumerator方法,这样就可以使用foreach来遍历。此外,可能需要提到yield关键字,它简化了迭代器的实现,自动生成状态机来处理枚举过程。 应用场景方面,典型的例子包括LINQ查询,因为LINQ方法很多都是基于IEnumerable扩展的。延迟执行也是一个关键点,比如在数据库查询时,使用IQueryable(继承自IEnumerable)可以延迟执行,直到真正需要数据的时候才去查询数据库,提高效率。还有自定义集合的遍历,比如实现一个生成斐波那契数列的类,通过IEnumerable可以让用户方便地用foreach来获取数列。 需要注意的地方可能包括:IEnumerable是只读向前的,不能修改集合元素,也不能随机访问。如果需要更复杂的操作,可能需要使用其他接口,比如ICollection或IList。另外,延迟执行可能导致多次枚举的问题,如果数据源发生变化,可能会得到不同的结果。 还需要检查是否有相关的引用需要添加。用户提供的引用[1]提到IEnumerable用于遍历集合,以及和IQueryable的区别。这可能涉及到在讨论延迟执行时,可以提到IQueryable的延迟执行与数据库查询优化,而IEnumerable的延迟执行更多是在内存中处理。 可能用户还会混淆IEnumerable和IQueryable,不过问题主要集中在IEnumerable上。因此,在回答中可以适当提到它们的区别,但重点还是放在IEnumerable的作用和用法。 最后,生成相关问题,比如性能考虑、与IQueryable的区别、如何实现自定义集合等,这样用户如果有进一步的问题可以继续探索。 现在需要把这些思路组织成结构化的回答,确保符合用户的要求:使用中文,行内公式用$...$,独立公式用$$...$$,但这里可能不太需要数学公式。同时要添加引用,确保引用标识自然。代码示例部分需要用正确的语法,比如C#的例子,比如用yield return实现迭代器。 需要验证一下,比如在C#中实现IEnumerable接口是否需要同时实现IEnumerableIEnumerator,或者是否可以使用迭代器方法简化。实际上,从C# 2.0开始,使用yield return可以自动生成所需的类和状态机,所以代码示例应该展示这一点。 可能还需要提到命名空间System.Collections,因为IEnumerable接口位于这个命名空间下,使用时需要引入。 总结一下,回答的结构应该是: 1. 作用:统一遍历,支持foreach,延迟执行 2. 用法:实现接口,使用yield return,LINQ操作 3. 应用场景:LINQ查询,自定义集合,延迟加载 4. 注意事项:只读向前,多次枚举问题 5. 代码示例:自定义迭代器 6. 相关问题 确保每个部分都清晰,并且引用[^1]在适当的位置,比如提到IEnumerable的基础作用时。</think>### C# IEnumerable 接口详解 #### 一、作用与核心能力 1. **统一集合遍历机制** IEnumerable 是 .NET 中最基础的集合遍历接口,定义了一个标准方法 `GetEnumerator()`,返回实现 `IEnumerator` 接口的对象。这使得所有集合(如数组、列表、字典)都能通过 `foreach` 循环遍历,例如: ```csharp IEnumerable<int> numbers = new List<int>{1, 2, 3}; foreach (var num in numbers) { Console.WriteLine(num); } ``` 2. **支持延迟执行** 通过 `yield return` 实现迭代器时,数据生成会按需执行(延迟加载)。例如生成斐波那契数列时,不会一次性计算所有元素: ```csharp public static IEnumerable<int> Fibonacci() { int a = 0, b = 1; while (true) { yield return a; (a, b) = (b, a + b); } } ``` 3. **LINQ 查询基础** LINQ 扩展方法(如 `Where`、`Select`)通过 `IEnumerable<T>` 实现数据操作,例如: ```csharp var filtered = numbers.Where(x => x > 2); // 延迟执行,仅在遍历时过滤 ``` [^1]: IEnumerable 提供了通用遍历能力,是 LINQ 和集合操作的基础。 #### 二、典型应用场景 | 场景 | 说明 | 代码示例 | |------|------|----------| | **自定义集合遍历** | 实现可迭代的数据结构 | 见下方代码块 | | **数据流处理** | 按需生成大数据集(如日志分析) | `IEnumerable<LogEntry> ReadLogs()` | | **与 LINQ 集成** | 链式数据转换与过滤 | `users.Select(u => u.Name)` | #### 三、代码实现示例 ```csharp public class CustomCollection : IEnumerable<int> { private int[] _data = {10, 20, 30}; public IEnumerator<int> GetEnumerator() { foreach (int item in _data) { yield return item; // 实现按需返回 } } // 显式实现非泛型接口 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } ``` #### 四、关键注意事项 1. **只读向前迭代** IEnumerable 不提供修改集合或反向遍历的能力,需转换为 `List<T>` 或使用其他接口实现复杂操作。 2. **多次枚举问题** 延迟执行的查询在每次枚举时都会重新计算,可能导致性能损耗: ```csharp var query = numbers.Where(x => x % 2 == 0); var count1 = query.Count(); // 触发计算 var count2 = query.Count(); // 再次触发计算 ``` 3. **性能取舍** 对小型数据集直接使用 `List<T>` 更高效,大型数据集采用延迟加载可优化内存使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值