zoukankan      html  css  js  c++  java
  • C# ~ 从 IEnumerable / IEnumerator 到 IEnumerable<T> / IEnumerator<T> 到 yield

    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 GetEnumerator(){
             return persons.GetEnumerator();  // 方法 1
         }
    }

    [-2-]. 自定义枚举数类 (EnumeratorPeople 类如下),继承并实现 IEnumerator 接口,重新定义 People 类:

    public class People : IEnumerable
    {
         ... ...
         public IEnumerator GetEnumerator(){
             return new EnumeratorPeople(persons);  // 方法 2 
         }
    }

    此时,在方法2中自定义类可以使用如下 foreach 语句来遍历自身元素:

    foreach (var person in people)
        Console.WriteLine(person.ToString());
    • 定义EnumeratorPeople类 (MyEnumerator)
    public class EnumeratorPeople : IEnumerator
    {
         private int position = -1;
         private Person[] persons;
         public EnumeratorPeople(Person[] _persons){ 
             persons = new Person[_persons.Length];
             for (int i = 0; i < _persons.Length; ++i)
                 persons[i] = _persons[i];
         }
     
         public object Current{
             get{ return persons[position]; }
         }
         public bool MoveNext(){
             ++position;
             return (position < persons.Length); 
         }
         public void Reset(){
              position = -1;
         }
    }

    主函数测试代码

    class Program{
        static void Main(string[] args){
            Person[] persons = { 
                new Person("abc",25),  new Person("xyz",22),
                new Person("qwer",12), new Person("pm",20)  };
            People people = new People(persons);
        }
    }

    总结

    一个类型是否支持foreach遍历,本质上是实现 IEnumerator 接口,2 种方法:
    (1)自定义类只要继承 IEnumerable 接口并实现无参 GetEnumerator() 方法即可,最简单;
    (2)在(1)基础上,定义 MyEnumerator 类继承并实现 IEnumerator 接口;

    扩展

    foreach 语句隐式调用集合的无参 GetEnumerator() 方法。其实,不论集合是否有实现 IEnumerable 接口,只要必须提供无参 GetEnumerator() 方法并返回包含 Current 属性和 MoveNext() 方法的 IEnumerator 对象即可,然后编译器会自动去绑定,无需依赖 IEnumerable 和 IEnumerator 接口,从而实现 foreach 对自定义集合类的遍历。

     1   public class People
     2   {
     3     public class EnumeratorPeople
     4     { 
     5             private int position = -1;
     6             private Person[] enumPersons; 
     7             public EnumeratorPeople(Person[] _persons){
     8                 enumPersons = new Person[_persons.Length];
     9                 for (int i = 0; i < _persons.Length; ++i){
    10                     enumPersons[i] = _persons[i];
    11                 }
    12             }
    13 
    14             public object Current{
    15                 get { return enumPersons[position]; }
    16             }
    17             public bool MoveNext(){
    18                 ++position;
    19                 return ( position < enumPersons.Length );
    20             }
    21             public void Reset(){
    22                 position = -1;
    23             }
    24        }
    25 
    26         private Person[] persons;
    27         public People(Person[] _persons){
    28             persons = new Person[_persons.Length];
    29             for (int i = 0; i < _persons.Length; ++i){
    30                 persons[i] = _persons[i];
    31             }
    32         }
    33         public EnumeratorPeople GetEnumerator(){
    34             return new EnumeratorPeople(persons);
    35         }
    36   }

    此处,枚举数类声明为嵌套类,或者集成为一个类,也可以分成单独的 2 个类均可。

    延伸问题

    • for 与 foreach

    for 先取全部再遍历,foreach 边遍历边取值;

    • Linq to Object 中返回 IEnumerable 类型?

    IEnumerable 是延迟加载的。

    参考

    ·传统遍历与迭代器
    ·IEnumerable和IEnumerator 详解
    ·自定义类实现foreach深入理解 foreach


    IEnumerable<T> / IEnumerator<T>

    优缺点对比
    ·  非泛型:非类型安全,返回object类型的引用、需要再转化为实际类型 (值类型需要装箱和拆箱);
    ·  泛型:类型安全,直接返回实际类型的引用;
    首先,IEnumerable<T> / IEnumerator<T> 接口定义如下:

    public interface IEnumerable<out T> : IEnumerable  
    {
        IEnumerator<T> GetEnumerator();
    } 
    public interface IEnumerator<out T> : IEnumerator, IDisposable  
    {
        T Current { get; }
    }

    其中,接口 IDisposable 定义为:

    public interface IDisposable{
        void Dispose();
    }

    逻辑关系图:
       精减的团队模型 - R模型 

       People <-> MyClass 实现 IEnumerable<T> 接口的 泛型GetEnumerator()方法
       GenEnumPeople <-> MyGenEnumerator 实现 IEnumerator<T> 接口

    显式实现非泛型版本,在类中实现泛型版本!如下,类 MyGenEnumerator 实现了 IEnumerator<T>,类MyClass 实现了 IEnumerable<T> 接口。

     1 public class MyClass : IEnumerable<T>
     2 {
     3     public IEnumerator<T> GetEnumerator() { }     // IEnumerable<T> 版本
     4     IEnumerator IEnumerable.GetEnumerator() { }   // IEnumerable 版本
     5 }
     6 
     7 public class MyGenEnumerator : IEnumerator<T>
     8 {
     9     public T Current { get; }            // IEnumerator<T> 版本
    10     public bool MoveNext() { }
    11     public void Reset() { }
    12     object IEnumerator.Current { get; }  // IEnumerator 版本
    13     public void Dispose() { }
    14 }

    ·  定义 People 类 (MyClass)  

     1    public class People : IEnumerable<Person>
     2    {
     3         private Person[] persons;
     4         public People(Person[] _persons){
     5             persons = new Person[_persons.Length];
     6             for (int i = 0; i < _persons.Length; ++i)
     7                 persons[i] = _persons[i];
     8         }
     9 
    10         public IEnumerator<Person> GetEnumerator(){
    11             return new GenericEnumeratorPeople(persons);
    12         }
    13         IEnumerator IEnumerable.GetEnumerator(){   // 显式实现
    14             return this.GetEnumerator();
    15         }
    16    }

    ·  定义 GenEnumPeople 类 (MyGenEnumerator)

     1    public class GenEnumPeople : IEnumerator<Person>
     2    {
     3         private int position = -1;
     4         private Person[] persons;
     5         public GenericEnumeratorPeople(Person[] _persons){ 
     6             persons = new Person[_persons.Length];
     7             for (int i = 0; i < _persons.Length; ++i)
     8                 persons[i] = _persons[i];
     9         }
    10 
    11         public Person Current{
    12             get { return persons[position]; }
    13         }
    14         object IEnumerator.Current{    // 显式实现
    15             get { return this.Current; }
    16         }
    17         public bool MoveNext(){
    18             ++position;
    19             return (position < persons.Length);
    20         }
    21         public void Reset() { position = -1; }
    22         public void Dispose() { Console.WriteLine("void Dispose()"); }
    23    }

    其中,IDisposable 接口的学习参见 由 IDisposable 到 GC

    扩展 泛型委托应用


    迭代器

      C#2.0 利用迭代器可以简单实现 GetEnumerator() 函数。迭代器是用于返回相同类型的值的有序集合的一段代码。利用 yield 关键字,实现控制权的传递和循环变量的暂存,使类或结构支持 foreach 迭代,而不必显式实现 IEnumerable 或 IEnumerator 接口,由 JIT 编译器辅助编译成实现了 IEnumerable 或 IEnumerator 接口的对象。yield return 提供了迭代器一个重要功能,即取到一个数据后马上返回该数据,不需要全部数据加载完毕,有效提高遍历效率(延迟加载)。
     - yield 关键字用于指定返回的值,yield return 语句依次返回每个元素,yield break 语句终止迭代;
     - 到达 yield return 语句时,保存当前位置,下次调用迭代器时直接从当前位置继续执行;
     - 迭代器可以用作方法、运算符或get访问器的主体实现,yield 语句不能出现在匿名方法中;
     - 迭代器返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>;
     
    逻辑关系图: 
       精减的团队模型 - R模型 

    · · 返回 IEnumerator 类型

     返回此类型的迭代器通常为默认迭代器
     ·  People 类

     1  public class People
     2  {
     3    private Person[] persons;
     4    public People(Person[] _persons){
     5       persons = new Person[_persons.Length];
     6       for (int i = 0; i < _persons.Length; ++i)
     7          persons[i] = _persons[i];
     8    }
     9 
    10    public IEnumerator GetEnumerator(){
    11       return IterMethod1;      // [1]
    12       return IterMethod2();    // [2]
    13    }
    14    public IEnumerator IterMethod1     // [1].属性返回 IEnumerator
    15    {
    16       get {
    17          for (int i = 0; i < persons.Length; ++i)
    18             yield return persons[i];
    19       }
    20    }
    21    public IEnumerator IterMethod2()   // [2].函数返回 IEnumerator (推荐)
    22    {
    23       for (int i = 0; i < persons.Length; ++i)
    24          yield return persons[i];
    25    }
    26  }

    ·主函数测试代码

    1  People people = new People(persons);
    2  foreach (var person in people)
    3     Console.WriteLine(person.ToString());

    · ·返回 IEnumerable 类型

     返回此类型的迭代器通常用于实现自定义迭代器
     ·  People 类

     1  public class People
     2  {
     3     private Person[] persons;
     4     public People(Person[] _persons){
     5       persons = new Person[_persons.Length];
     6       for (int i = 0; i < _persons.Length; ++i)
     7          persons[i] = _persons[i];
     8     }
     9    
    10     public IEnumerator GetEnumerator(){
    11       return IterMethod1.GetEnumerator();      // [1]
    12       return IterMethod2().GetEnumerator();    // [2]
    13     }
    14     public IEnumerable IterMethod1    // [1].自定义迭代器 1
    15     {
    16       get{
    17          for (int i = 0; i < persons.Length; ++i)
    18             yield return persons[i];
    19       }
    20     }
    21     public IEnumerable IterMethod2()  // [2].自定义迭代器 2 (推荐)
    22     {
    23       for (int i = 0; i < persons.Length; ++i)
    24          yield return persons[i];
    25     }
    26  }

    ·主函数测试代码

    1  People people = new People(persons);
    2  foreach (var person in people)   // 默认
    3     Console.WriteLine(person.ToString());
    4  foreach (var person in people.IterMethod1)    // [1]
    5     Console.WriteLine(person.ToString());
    6  foreach (var person in people.IterMethod2())  // [2]
    7     Console.WriteLine(person.ToString());

     对于返回泛型IEnumerator<T>、IEnumerable<T>的迭代器,同理。

    参考

    ·foreach 与 yield

  • 相关阅读:
    aspcms产品详情页调取相关产品
    构造函数中返回一个对象对结果有什么影响
    跨域的几种方法及案例代码
    localStorage兼容方案
    H5 拖放事件详解
    由作用域安全的构造函数想到的
    valueOf和toString的区别
    网页布局--自适应
    【MongoDB系列】简介、安装、基本操作命令
    【JavaWeb】之Servlet
  • 原文地址:https://www.cnblogs.com/wjcx-sqh/p/5929886.html
Copyright © 2011-2022 走看看