C# IEnumerable、IEnumerator和foreach的联系与解析

1、关于foreach和for

foreach和for都是循环的关键字,使用这两个关键字可以对集合对象进行遍历,获取里面每一个对象的信息进行操作。

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

上面结果的输出都是一样的,我们来看看IL是否是一样的。

 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行

从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”是一个“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();
        }
    }

运行出现了异常

可以看出移除IEnumerable类型的变量也会出错,所以在foreach中是不能改变进行迭代的集合对象值的。

2、foreach和IEnumerable的联系

像List,Array等集合类型,可以使用for和foreach来对其进行循环迭代,获得每一个集合内的对象用于操作。之所以可以使用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类型为例,来看看这个接口是怎么实现的。

首先内部有一个数组变量用于存储遍历的集合对象。

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中都有这个判断,如果枚举值被修改了,他所对应的版本号将会发生改变(在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;
            }
        }
    }
时间: 2024-11-09 16:22:28

C# IEnumerable、IEnumerator和foreach的联系与解析的相关文章

C#中的IEnumerator、foreach、yield

[C#中的IEnumerator.foreach.yield] 1.IEnumerator,是一个接口,它的方法如下: 2.foreach语句,在编译后会变成IEnumerator的调用: 3.yield用于return一个IEnumerator. 参考:http://wenku.baidu.com/link?url=3wR-rCcSOQtNOGcz3uaWG_EAyGfRqcNER1jbOEd7H57qw4ZRWnWatpAO6_WkAzUSRPorCXdy7vnT4I23tvPFpcX8xO

为IEnumerable扩展一个ForEach方法

IEnumerable没有一个ForEach方法,我们可以使用C#写一个扩展方法: Source Code: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Insus.NET.ExtendMethods { public static class Enumerables { public st

飞波拿鸡(要定义IEnumerable&lt;T&gt;的ForEach扩展方法):

var N = 20; var list = Enumerable.Range(0, N).ToArray(); N = list.ForEach(n => list[n] = n < 2 ? n : list[n - 1] + list[n - 2]).Last(); 飞波拿鸡(要定义IEnumerable<T>的ForEach扩展方法):

IEnumerable &amp; IEnumerator

IEnumerable 只有一个方法:IEnumerator GetEnumerator(). INumerable 是集合应该实现的一个接口,这样,就能用 foreach 来遍历这个集合. IEnumerator 有Current属性,MoveNext(), Reset()两个方法. 当 foreach 使用到一个 IEnumerable 的集合上的时候,遍历是这样开始的: 1. 调用 GetEnumerator() 得到一个 IEnumerator 的对象. 2. 调用 MoveNext()

c#yield,IEnumerable,IEnumerator

foreach 在编译成IL后,实际代码如下: 即:foreach实际上是先调用可枚举对象的GetEnumerator方法,得到一个Enumerator对象,然后对Enumerator进行while循环的相关操作,然后得到可枚举对象中的每一个值. 可以把可枚举对象中的所有值想像成一个链表,Enumerator是链表的指针,Enumerator.Current是当前指向的元素,Enumerator.MoveNext是指针后移.于是用while循环便可以用类似遍历链表的方式得到对象中的所有值. 一个

C# .net用法大全

从事多年的开发,对于.net可以说有一定的总结,有关于教科书般的文档,献于交流. 本文整理了当前企业web开发中的管理系统,商城等系统的常用开发技术栈. C#常见运算符 一元运算符(+.-.!.~.++.--) 算术运算符(*./.%.+ . – ) 移位运算符(<< .>> ) 关系和类型测试运算符(==.!=.<.>.<=.>=.is 和 as) 逻辑运算符(&.^ 和 | ) 条件逻辑运算符(&& 和 || ) 空合并运算符(?

IEnumerable和IEnumerator 详解

http://blog.csdn.net/byondocean/article/details/6871881 初学C#的时候,老是被IEnumerable.IEnumerator.ICollection等这样的接口弄的糊里糊涂,我觉得有必要切底的弄清楚IEnumerable和IEnumerator的本质. 下面我们先看IEnumerable和IEnumerator两个接口的语法定义.其实IEnumerable接口是非常的简单,只包含一个抽象的方法GetEnumerator(),它返回一个可用于

C# IEnumerable和IEnumerator的区别,如何实现

IEnumerable接口和IEnumerator接口是.NET中非常重要的接口,二者有何区别? 1. 简单来说IEnumerable是一个声明式的接口,声明实现该接口的类就是"可迭代的enumerable",但并没用说明如何实现迭代器(iterator).其代码实现为: public interface IEnumerable         {                IEnumerator GetEnumerator();          }    2. 而IEnumer

C# IEnumerator与 IEnumerable

1. 接口的使用 (1)  首先定义接口 public interface IBattleMapManager : { Stages CurrentStage { get; } event EventHandler<BeginFightEventArgs> EnterFight; } (2) 用定义实现类- 实现接口 public class BattleMapManager : IBattleMapManager, IDisposable { public Stages CurrentSta