IEnumerator和foreach的关联与分析

IEnumerator和IEnumerable

从名字常来看,IEnumerator是枚举器的意趣,IEnumerable是可枚举的意味。
刺探了三个接口代表的意义后,接着看源码:
IEnumerator:

public interface IEnumerator
    {
        // Interfaces are not serializable
        // Advances the enumerator to the next element of the enumeration and
        // returns a boolean indicating whether an element is available. Upon
        // creation, an enumerator is conceptually positioned before the first
        // element of the enumeration, and the first call to MoveNext 
        // brings the first element of the enumeration into view.
        // 
        bool MoveNext();

        // Returns the current element of the enumeration. The returned value is
        // undefined before the first call to MoveNext and following a
        // call to MoveNext that returned false. Multiple calls to
        // GetCurrent with no intervening calls to MoveNext 
        // will return the same object.
        // 
        Object Current {
            get; 
        }

        // Resets the enumerator to the beginning of the enumeration, starting over.
        // The preferred behavior for Reset is to return the exact same enumeration.
        // This means if you modify the underlying collection then call Reset, your
        // IEnumerator will be invalid, just as it would have been if you had called
        // MoveNext or Current.
        //
        void Reset();
    }

IEnumerable:

    public interface IEnumerable
    {
        // Interfaces are not serializable
        // Returns an IEnumerator for this enumerable Object.  The enumerator provides
        // a simple way to access all the contents of a collection.
        [Pure]
        [DispId(-4)]
        IEnumerator GetEnumerator();
    }

发觉IEnumerable唯有一个GetEnumerator函数,再次来到值是IEnumerator类型,从注释大家能够识破IEnumerable代表继续此接口的类能够博得一个IEnumerator来兑现枚举这几个类中包涵的聚众中的成分的效益(举例List<T>,ArrayList,Dictionary等两次三番了IEnumeratble接口的类)。

1.假设您回去的汇集是只用于遍历,不可修改的,则赶回IEnumerable<T>
二.万3次到的联谊须要修改,如增加和删除元素,用ICollection<T>
澳门葡京备用网址 ,三.假设回到的会集须求协理排序,索引等,用IList<T>
肆.万二次到的汇集要支持索引,但不可能加上,删除成分,用ReadOnlyCollection<T>
 
ICollection重要针对静态会集;IList首要针对动态集结
IEnumerable<T>继承自IEnumerable
ICollection<T>继承自IEnumerable<T>
IList<T>继承自ICollection<T>
IEnumerable接口 
IEnumerator和foreach的关联与分析。    达成了IEnumerable接口的集聚注明该集结能够提供三个enumerator(枚举器)对象,协理当前的遍历集结。IEnumerable接口唯有三个分子GetEnumerator()方法。
IEnumerator接口是只读的,包蕴以下四个分子:       
MoveNext()方法调节遍历指针移向集结的下1个要素。注意,遍历指针的发轫地方是聚众中首个成分的前方。要指向第四个因素,必须先调用一回MoveNext()方法。该方法重返2个布尔值,如若成功遍历到下一个成分,则赶回true;如果指针移出末尾,则赶回false。 
Reset()方法用于安装遍历指针指向初阶地方,即聚聚集第一个因素的后面。 
Current属性重返集合中当前线指挥部标的引用。 
IEnumerable和IEnumerator的区别:
1、1个Collection要帮助foreach格局的遍历,必须达成IEnumerable接口(亦即,必须以某种格局赶回IEnumerator object)。 
    二、IEnumerator object具体得以完毕了iterator(通过MoveNext(),Reset(),Current)。 
    3、从这七个接口的用词采纳上,也足以观察其区别:IEnumerable是一个表明式的接口,注明落成该接口的class是“可枚举(enumerable)”的,但并不曾注脚什么促成枚举器(iterator);IEnumerator是三个达成式的接口,IEnumerator object便是三个iterator。 
    4、IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法创制了连接,client能够由此IEnumerable的GetEnumerator()获得IEnumerator object,在那么些意思上,将GetEnumerator()看作IEnumerator object的factory method也未尝不可。
 
ICollection集合 
   该接口是IEnumerable接口的子接口,定义了聚众的尺寸、IEnumerator接口和协同方法。在IEnumerable接口的功底上加码了以下职能。 
Count;该属性重返集结夷则素的数据。 
CopyTo(Array   array,   int   index);该方法用于贯彻从集结中拷贝成分到3个一维数组中。 
IList接口 
   Add()和Insert()方法用于向聚集中加多条款,使用索引来钦命项目要插入的职分,当中首成分的目录为0;Add()将新条款加多到尾巴部分。 
Remove()和RemoveAt()用于从列表中剔除条约。Clear()用于删除全体条条框框。 
IndexOf和Contains()用于寻觅该列表。 
Item属性用于获取或安装索引钦赐的值。C#中可以运用[]运算符实行访问。
 
IList接口和ArrayList类的目标是落到实处动态数组,ArrayList是IList的二个兑现。
 
IDictionary接口 
IDictionary接口是3个蕴涵1组”关键字/值”对的数据结构,各样值都由相应的入眼字来定义。关键字和值能够是其余数据类型,关键字必须唯壹且非空。 
Add()方法增多3个钦定的严重性字和值的条目款项到IDictionary接口。 
Item属性检索内定关键字所对应的值。 
Keys和Values属性分别重回包罗全数主要字和值的成团。 
Remove()用于删除钦点关键字对应的条条框框。Clear()用于删除全体条条框框。 
GetEnumator()再次来到叁个IDictionaryEnumerator,可用来遍历IDictionary接口。
 
 
ObservableCollectin<T>是从Collection<T>承接而来的。
 
 
何以时候要用List<T>:
要对集中数据举办拍卖,从中筛选数据照旧排序

1、关于foreach和for

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
语句来遍历本身因素。逻辑关系图:
   澳门葡京备用网址 1

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

·  定义Person类

 1  public class Person
 2  {
 3    private string name;
 4    private int age;
 5 
 6    public Person(string _name, int _age){
 7       this.name = _name, this.age = _age;
 8    }
 9    public override string ToString(){
10       return string.Format("Name:{0},Age:{1}.", name, age);
11    }
12  }

  数组定义见主函数,以下2种遍历数组的法子等同,因为全部数组的基类皆以System.Array ,System.Array 类落成了 IEnumerable 接口,能够一直通过
GetEnumerator()
方法再次回到枚举数对象,枚举数对象能够依次再次来到请求的数组的要素。

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

·  定义People类 (MyClass)

 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   }

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

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

 方法 2:People 类承袭 IEnumerable 接口并完毕 GetEnumerator()
方法,有 贰 种方法:
  [-1-]. 利用数组暗中同意实现了
IEnumerable 和 IEnumerator 接口的实际,重新定义 People 类:

1   public class People : IEnumerable
2   {
3         ... ...
4         public IEnumerator GetEnumerator(){
5             return persons.GetEnumerator();  // 方法 1
6         }
7   }

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

1   public class People : IEnumerable
2   {
3         ... ...
4         public IEnumerator GetEnumerator(){
5             return new EnumeratorPeople(persons);  // 方法 2 
6         }
7   }

 此时,在方法2中自定义类能够利用如下 foreach 语句来遍历自个儿因素:

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

·  定义EnumeratorPeople类
(MyEnumerator)

 1   public class EnumeratorPeople : IEnumerator
 2   {
 3         private int position = -1;
 4         private Person[] persons;
 5         public EnumeratorPeople(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 object Current{
12             get{ return persons[position]; }
13         }
14         public bool MoveNext(){
15             ++position;
16             return (position < persons.Length); 
17         }
18         public void Reset(){
19             position = -1;
20         }
21   }

·主函数测试代码

1     class Program{
2         static void Main(string[] args){
3             Person[] persons = { 
4                 new Person("abc",25),  new Person("xyz",22),
5                 new Person("qwer",12), new Person("pm",20)  };
6             People people = new People(persons);
7         }
8     }

总结

 3个档案的次序是或不是扶助foreach遍历,本质上是落到实处 IEnumerator 接口,2种方法:
(一)自定义类只要继续 IEnumerable 接口并达成无参 GetEnumerator()
方法就可以,最简便;
(二)在(一)基础上,定义 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个类,也得以分为单独的 1个类均可。

延伸难题
·  for 与 foreach
 for 先取全方位再遍历,foreach 边遍历边取值;
·  Linq to Object 中返回 IEnumerable 类型?
 IEnumerable 是延迟加载的。

参考

·
古板遍历与迭代器;
·  IEnumerable和IEnumerator
详解;
·
自定义类达成foreach;深切精通foreach;


用foreach来通晓IEnumerable,IEnumerator的办事规律

我们模仿ArrayList来落成1个轻便的ConstArrayList,然后用foreach遍历。

//一个常量的数组,用于foreach遍历
class ConstArrayList : IEnumerable
{
    public int[] constItems = new int[] { 1, 2, 3, 4, 5 };
    public IEnumerator GetEnumerator()
    {
        return new ConstArrayListEnumeratorSimple(this);
    }
}
//这个常量数组的迭代器
class ConstArrayListEnumeratorSimple : IEnumerator
{
    ConstArrayList list;
    int index;
    int currentElement;
    public ConstArrayListEnumeratorSimple(ConstArrayList _list)
    {
        list = _list;
        index = -1;
    }

    public object Current
    {
        get
        {
            return currentElement;
        }
    }

    public bool MoveNext()
    {
        if(index < list.constItems.Length - 1)
        {
            currentElement = list.constItems[++index];
            return true;
        }
        else
        {
            currentElement = -1;
            return false;
        }
    }

    public void Reset()
    {
        index = -1;
    }
}
class Program
{    
    static void Main(string[] args)
    {
        ConstArrayList constArrayList = new ConstArrayList();
        foreach(int item in constArrayList)
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

出口结果:
1
2
3
4
5

代码达到了遍历效果,但是在用foreach遍历时,IEnumerator和IEnumerable毕竟是哪些运转的,大家得以透过增添扩张日志能够直观的看看原因。

//一个常量的数组,用于foreach遍历
class ConstArrayList : IEnumerable
{
    public int[] constItems = new int[] { 1, 2, 3, 4, 5 };
    public IEnumerator GetEnumerator()
    {
        WriteLine("GetIEnumerator");
        return new ConstArrayListEnumeratorSimple(this);
    }
}
//这个常量数组的迭代器
class ConstArrayListEnumeratorSimple : IEnumerator
{
    ConstArrayList list;
    int index;
    int currentElement;
    public ConstArrayListEnumeratorSimple(ConstArrayList _list)
    {
        list = _list;
        index = -1;
    }

    public object Current
    {
        get
        {
            WriteLine("Current");
            return currentElement;
        }
    }

    public bool MoveNext()
    {
        if(index < list.constItems.Length - 1)
        {
            WriteLine("MoveNext true");   
            currentElement = list.constItems[++index];
            return true;
        }
        else
        {
            WriteLine("MoveNext false");
            currentElement = -1;
            return false;
        }
    }

    public void Reset()
    {
        WriteLine("Reset");
        index = -1;
    }
}
class Program
{    
    static void Main(string[] args)
    {
        ConstArrayList constArrayList = new ConstArrayList();
        foreach(int item in constArrayList)
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

出口结果:
GetIEnumerator
MoveNext true
Current
1
MoveNext true
Current
2
MoveNext true
Current
3
MoveNext true
Current
4
MoveNext true
Current
5
MoveNext false

通过输出结果,大家得以窥见,foreach在运营时会先调用ConstArrayList的GetIEnumerator函数获取二个ConstArrayListEnumeratorSimple,之后通过循环调用ConstArrayListEnumeratorSimple的MoveNext函数,index后移,更新Current属性,然后回到Current属性,直到MoveNext重临false。

总计一下:
GetIEnumerator()负担获取枚举器。
MoveNext()负担让Current获取下多个值,并认清遍历是还是不是终止。
Current担当重返当前针对的值。
Rest()肩负重新设置枚举器的气象(在foreach中尚无应用)
这一个就是IEnumerable,IEnumerator的着力专门的工作规律了。

附带大家发掘:

ConstArrayList constArrayList = new ConstArrayList();
foreach(int item in constArrayList)
{
    writeLine(item);
}

其实就等价于:

ConstArrayList constArrayList = new ConstArrayList();
IEnumerator enumeratorSimple = constArrayList.GetEnumerator();
while (enumeratorSimple.MoveNext())
{
    int item = (int)enumeratorSimple.Current;
    WriteLine(item);
}

相当于说foreach其实是1种语法糖,用来简化对可枚举成分的遍历代码。而被遍历的类经过完成IEnumerable接口和三个有关的IEnumerator枚举器来落到实处遍历功用。

foreach和for都以循环的首要字,使用那三个至关心保养要字能够对集中对象开始展览遍历,获取里面每2个目的的音信举办操作。

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();
}

逻辑关系图:
   澳门葡京备用网址 2 

   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;

扩展
:泛型委托行使;


  static void Main(string[] args)
        {
            string[] strList = new string[]
            {
                "1","2","3","4"
            };

            for (int i = 0; i < strList.Length; i++)
            {
                Console.WriteLine(strList[i]);
            }

            foreach (string str in strList)
            {
                Console.WriteLine(str);
            }

            Console.ReadKey();
        }

迭代器

  C#2.0 利用迭代器能够省略实现 GetEnumerator()
函数。迭代器是用以再次来到同样档次的值的平稳集中的壹段代码。利用 yield
关键字,达成调控权的传递和循环变量的暂存,使类或协会协助 foreach
迭代,而不要显式落成 IEnumerable 或 IEnumerator 接口,由 JIT
编写翻译器辅助理编辑译成落成了 IEnumerable 或 IEnumerator 接口的目的。yield return
提供了迭代器3个重中之重意义,即取到2个数量后立即重返该数量,无需全数数目加载完成,有效压实遍历成效(延迟加载)。
  yield 关键字用于钦点重临的值,yield return
语句依次再次来到每种成分,yield
break 语句终止迭代;
  达到 yield return
语句时,保存当前岗位,下次调用迭代器时直接从脚下岗位继续推行;
  迭代器能够当作方法、运算符或get访问器的重头戏完结,yield
语句不可能冒出在无名方式中;
  迭代器再次来到类型必须为
IEnumerable、IEnumerator、IEnumerable<T> 或
IEnumerator<T>;
 
逻辑关系图: 
   澳门葡京备用网址 3 

地点结果的出口都以均等的,我们来探望IL是不是是同样的。

· · 返回 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());
 1 IL_002c:  br.s       IL_003d   //for开始的地方
 2   IL_002e:  nop
 3   IL_002f:  ldloc.0
 4   IL_0030:  ldloc.1
 5   IL_0031:  ldelem.ref
 6   IL_0032:  call       void [mscorlib]System.Console::WriteLine(string)
 7   IL_0037:  nop
 8   IL_0038:  nop
 9   IL_0039:  ldloc.1  //
10   IL_003a:  ldc.i4.1 //
11   IL_003b:  add      //索引加1,这里的索引是已经保存在堆栈中的索引
12   IL_003c:  stloc.1
13   IL_003d:  ldloc.1
14   IL_003e:  ldloc.0
15   IL_003f:  ldlen
16   IL_0040:  conv.i4
17   IL_0041:  clt
18   IL_0043:  stloc.s    CS$4$0001
19   IL_0045:  ldloc.s    CS$4$0001
20   IL_0047:  brtrue.s   IL_002e   //跳转到第2行
21   IL_0049:  nop
22   IL_004a:  ldloc.0
23   IL_004b:  stloc.s    CS$6$0002
24   IL_004d:  ldc.i4.0
25   IL_004e:  stloc.s    CS$7$0003
26   IL_0050:  br.s       IL_0067   //foreach开始的地方
27   IL_0052:  ldloc.s    CS$6$0002
28   IL_0054:  ldloc.s    CS$7$0003
29   IL_0056:  ldelem.ref
30   IL_0057:  stloc.2
31   IL_0058:  nop
32   IL_0059:  ldloc.2
33   IL_005a:  call       void [mscorlib]System.Console::WriteLine(string)
34   IL_005f:  nop
35   IL_0060:  nop
36   IL_0061:  ldloc.s    CS$7$0003  //
37   IL_0063:  ldc.i4.1              //
38   IL_0064:  add                   //当前索引处加1
39   IL_0065:  stloc.s    CS$7$0003
40   IL_0067:  ldloc.s    CS$7$0003
41   IL_0069:  ldloc.s    CS$6$0002
42   IL_006b:  ldlen
43   IL_006c:  conv.i4
44   IL_006d:  clt
45   IL_006f:  stloc.s    CS$4$0001
46   IL_0071:  ldloc.s    CS$4$0001
47   IL_0073:  brtrue.s   IL_0052     //跳转到27行

· ·返回 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;

 

从IL能够看看,for中循环的目录是for本人的目录(即i),foreach在循环进度中会在钦定地点存款和储蓄四个值,那一个值便是循环用的目录。所以,其实foreach内部照旧存款和储蓄了二个索引值用于循环,只是大家在用的进度中并未有发觉到存在这么些变量而已。

大家再来看看上面这么些例子:

 static void RunFor()
        {
            string[] strList = new string[]
            {
                "1","2","3","4"
            };

            for (int i = 0; i < strList.Length; i++)
            {
                strList[i] = "1";
            }
        }

        static void RunForeach()
        {
            string[] strList = new string[]
            {
                "1","2","3","4"
            };

            foreach (string str in strList)
            {
                str = "1";
            }
        }

编写翻译出错 : “str”是1个“foreach 迭代变量”,不可能为它赋值

 static void RunFor()
        {
            List<string> strList = new List<string>()
            {
                "1","2","3","4"
            };

            for (int i = 0; i < strList.Count; i++)
            {
                strList[i] = "1";
            }
        }

        static void RunForeach()
        {
            List<string> strList = new List<string>()
            {
                "1","2","3","4"
            };

            foreach (string str in strList)
            {
                str = "1";
            }
        }

同等,编写翻译器给出了平等的错误。

那么壹旦在foreach中移除当前项呢?

class Program
    {
        static void Main(string[] args)
        {
            List<string> strs = new List<string>() { "1", "2", "3", "4" };
            foreach (string str in strs)
            {
                strs.Remove(str);
            }
            Console.ReadKey();
        }
    }

运维出现了非常

澳门葡京备用网址 4

可以见到移除IEnumerable类型的变量也会出错,所以在foreach中是无法更动实行迭代的集中对象值的。

2、foreach和IEnumerable的联系

像List,Array等聚焦类型,能够利用for和foreach来对其展开循环迭代,得到每3个凑合内的对象用于操作。之所以能够运用foreach,是因为List,Array等项目达成了IEnumerable只怕IEnumerable<T>接口。

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerable接口内部唯有贰个形式,GetEnumerator()方法,重返值是一个IEnumerator类型的对象。

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

可以看看,在IEnumerator接口中有四个成员,用于移动地方的MoveNext函数,表示近年来目标的Current属性,重新初始化函数Reset。

 大家以ArrayList类型为例,来看看这几个接口是怎么得以达成的。

第壹内部有2个数组变量用于存款和储蓄遍历的成团对象。

object[] _items;

在其间私有的类ArrayListEnumeratorSimple中贯彻了IEnumerator接口成员。

 1  public bool MoveNext()
 2     {
 3         int num;
 4         if (this.version != this.list._version)
 5         {
 6             throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
 7         }
 8         if (this.isArrayList)
 9         {
10             if (this.index < (this.list._size - 1))
11             {
12                 num = this.index + 1;
13                 this.index = num;
14                 this.currentElement = this.list._items[num]; //其实还是取得内部的数组变量的成员
15                 return true;
16             }
17             this.currentElement = dummyObject;
18             this.index = this.list._size;
19             return false;
20         }
21         if (this.index < (this.list.Count - 1))
22         {
23             num = this.index + 1;
24             this.index = num;
25             this.currentElement = this.list[num]; //数组变量的成员
26             return true;
27         }
28         this.index = this.list.Count;
29         this.currentElement = dummyObject;
30         return false;
31     }

 在MoveNext中开始展览迭代轮回的时候迭代的是中间的_items数组,即每便取的值都是_items的成员,而_items数组是ArrayList的索引数组。每便迭代后都会保留当前索引值用于下次采用。

 所以轻易看出,IEnumerator接口内部贯彻的点子终归如故和for达成的点子同样的。

据此修改枚举值过后继续走访会抛出InvalidOperationException格外是因为以下代码:

 if (this.version != this.list._version)
  {
     throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
  }

在Reset和MoveNext中都有其1论断,假设枚举值被涂改了,他所对应的本子号将会发出改变(在Remove函数中校会实施this._version++,使得版本号爆发了改换,其余改造枚举值状态的函数类似)。

3、自定义完成迭代器

 具体达成代码:

 class Program
    {
        static void Main(string[] args)
        {
            TestIEnumerable test = new TestIEnumerable();
            foreach (string str in test)
            {
                Console.WriteLine(str);
            }
            Console.ReadKey();
        }
    }

    class TestIEnumerable : IEnumerable
    {
        private string[] _item;

        public TestIEnumerable()
        {
            _item = new string[]
             {
                 "1","2","3","4"
             };
        }

        public string this[int index]
        {
            get { return _item[index]; }
        }

        public IEnumerator GetEnumerator()
        {
            return new EnumeratorActualize(this);
        }

        class EnumeratorActualize : IEnumerator
        {
            private int index;
            private TestIEnumerable _testEnumerable;
            private object currentObj;
            public EnumeratorActualize(TestIEnumerable testEnumerable)
            {
                _testEnumerable = testEnumerable;
                currentObj = new object();
                index = -1;
            }


            public object Current
            {
                get
                {
                    return currentObj;
                }
            }

            public bool MoveNext()
            {
                if (index < _testEnumerable._item.Length - 1)
                {
                    index++;
                    currentObj = _testEnumerable._item[index];
                    return true;
                }
                index = _testEnumerable._item.Length;
                return false;
            }

            public void Reset()
            {
                index = -1;
            }
        }
    }

 

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website