c# 自己实现可迭代的容器

     在c#中我们经常使用到foreach语句来遍历容器,如数组,List,为什么使用foreach语句能够遍历一个这些容器呢,首先的一个前提是这些容器都实现了IEnumerable接口,通过IEnumerable接口的GetEnumerator方法获得实现IEnumerator接口的对象。IEnumerator接口对象定义了两个方法外加一个属性。分别为MoveNext方法和Reset方法,以及Current属性。

一、foreach背后的故事     

下面我们通过一个简单的例子来探索foreach背后究竟发生了什么,进而我们自己实现一个简单的可迭代的容器。

namespace CustomEnumerateTest
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> l = new List<int>();
            l.Add(1);
            l.Add(2);
            l.Add(3);
            foreach (var v in l)
            {
                Console.WriteLine(v);
            }
        }
    }
}

这是一段很简单的代码,foreach究竟是如何来实现容器对象的遍历的,我们通过ILDasm工具来查看c#编译器生成的中间码。代码如下,只贴出部分中间码:

  IL_0020: ldloc.0
  IL_0021: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() //调用List的GetEnumerator方法获取GetEnumerator对象

     IL_0026: stloc.2

 .try
  {
    IL_0027:  br.s       IL_003a     //跳转指令,跳转到IL_003a处执行
    IL_0029:  ldloca.s   CS$5$0000   //获取Enumerator对象的地址,push到堆栈。
    IL_002b:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() //调用Enumerator对象的get_Current()方法。
    IL_0030:  stloc.1
    IL_0031:  nop
    IL_0032:  ldloc.1
    IL_0033:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_0038:  nop
    IL_0039:  nop
    IL_003a:  ldloca.s   CS$5$0000
    IL_003c:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()//调用Enumerator对象的MoveNext()方法。
    IL_0041:  stloc.3
    IL_0042:  ldloc.3
    IL_0043:  brtrue.s   IL_0029
    IL_0045:  leave.s    IL_0056
  }  // end .try
  finally
  {
    IL_0047:  ldloca.s   CS$5$0000
    IL_0049:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
    IL_004f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0054:  nop
    IL_0055:  endfinally
  }  // end handler

     上面贴出的就是对应源码中的foreach块的代码 。从红色部分我们可以看到了。首先编译器在遇到foreach语句时,会先调用List的GetEnumerator方法获得Enumerator对象,其中Enumerator对象实现了IEnumerator接口,GetEnumerator方法是IEnumerable接口,List实现了该接口。其次,编译器分别调用Enumerator对象的MoveNext方法,和Current方法获取对象的当前元素,这里是int类型的元素,不断循环直到条件为假,中间码IL_0043指令处的条件判断用于判断是否结束循环。

既然我们看到了foreach背后的真实的调用,那么现在我们自己实现一个简单的可迭代的容器,以便加深我们对IEnumerale和IEnumerator接口的理解。

二、简单的可迭代容器实现(本代码只是为了说明问题,对于各种异常情况没有做相应处理。

  首先来看一个没有实现IEnumerable的容器。

 1    public class SimpleList<TIn> 
 2     {
 3         private static TIn[] _element;
 4         private const int DefaultSize = 5;
 5         private int _currentIndex = -1;
 6         private int _allocSize;
 7         private int _length;
 8         public SimpleList()
 9         {
10             _element = new TIn[DefaultSize];
11             _allocSize = DefaultSize;
12 
13         }
14 
15         public TIn this[int index] { get { return _element[index]; } set { _element[index] = value; } }
16         public void Add(TIn value)
17         {
18 
19             _currentIndex++;
20             if (_currentIndex >= DefaultSize)
21             {
22                 _allocSize = _allocSize * 2;
23                 TIn[] tmp = _element;
24                 _element = new TIn[_allocSize];
25                 tmp.CopyTo(_element, 0);
26                 
27             }
28             _element[_currentIndex] = value;
29             _length++;
30         }
31         public int Length { get { return _length; }}
32 
33     }

这个SimpleList没有实现IEnumerable接口,所以我们不能通过foreach来访问它,编译器会提示类型is not enumerable。但是我们实现了Indexer,所以可以通过for语句来访问。

下面给SimpleList增加IEnumerable接口的实现完整代码:

 1   public class SimpleList<TIn> : IEnumerable<TIn>
 2     {
 3         private static TIn[] _element;
 4         private const int DefaultSize = 5;
 5         private int _currentIndex = -1;
 6         private int _allocSize;
 7         private int _length;
 8         public SimpleList()
 9         {
10             _element = new TIn[DefaultSize];
11             _allocSize = DefaultSize;
12 
13         }
14 
15         public TIn this[int index] { get { return _element[index]; } set { _element[index] = value; } }
16         public void Add(TIn value)
17         {
18 
19             _currentIndex++;
20             if (_currentIndex >= DefaultSize)
21             {
22                 _allocSize = _allocSize * 2;
23                 TIn[] tmp = _element;
24                 _element = new TIn[_allocSize];
25                 tmp.CopyTo(_element, 0);
26                 
27             }
28             _element[_currentIndex] = value;
29             _length++;
30         }
31         public int Length { get { return _length; }}
32 
33         public IEnumerator<TIn> GetEnumerator()
34         {
35             return new Enumerator(this);
36         }
37 
38         IEnumerator IEnumerable.GetEnumerator()
39         {
40             return GetEnumerator();
41         }
42         public struct Enumerator : IEnumerator<TIn>
43         {
44             private SimpleList<TIn> list;
45             private int curIndex;
46             private TIn current;
47 
48             internal Enumerator(SimpleList<TIn> l)
49             {
50                 list = l;
51                 curIndex = 0;
52                 current = default (TIn);
53             }
54             public void Dispose()
55             {
56 
57                 
58             }
59 
60             public bool MoveNext()
61             {
62                 if (curIndex < list.Length)
63                 {
64                     current = list[curIndex];
65                     curIndex++;
66                     return true;
67                 }
68                 return false;
69             }
70 
71             public void Reset()
72             {
73                 curIndex = 0;
74                 current = default (TIn);
75             }
76 
77             public TIn Current { get { return current; }}
78 
79             object IEnumerator.Current
80             {
81                 get
82                 {
83                     if (curIndex == 0 || curIndex == list.Length + 1)
84                     {
85                         throw new ArgumentException("curIndex");
86                     }
87                     return Current;
88                 }
89             }
90         }
91     }

现在我们可以通过foreach来遍历SimpleList容器对象了。我们分别通过for和foreach来遍历:

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5 
 6             SimpleList<int> sl = new SimpleList<int>();
 7             sl.Add(1);
 8             sl.Add(2);
 9             sl.Add(3);
10             Console.WriteLine("for 遍历:");
11             for (int i = 0; i < sl.Length; i++)
12             {
13                 Console.WriteLine(sl[i]);
14             }
15             Console.WriteLine("for each 遍历:");
16             foreach (var v in sl)
17             {
18                 Console.WriteLine(v);
19                 
20             }
21         
22          
23             
24 
25         }
26     }

程序运行结果:

三、总结

  通过以上的介绍我们实现迭代器对象首先是需要实现IEnumerate接口,其次为了遍历该对象中的元素我们需要实现IEnumerator接口,IEnumerate接口是为了获得Enumerator对象,只有获得了Enumerator对象我们才可以遍历集合的元素,这也是IEnumerate和IEnumerator的区别。IEnumerate接口告诉外界,该对象是可迭代的,具体如何迭代,是Enumerator接口实现的事情,因此,外界可以不需要知道Enumerator的存在。

  

原文地址:https://www.cnblogs.com/justinli/p/IEnumerable.html