• 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的存在。

      

  • 相关阅读:
    How To Write A Business Plan 如何撰写商业计划书 笔记
    从“蚁族”现象看高等教育公平
    C# POS机客显
    node vscode 调试
    项目已被 macOS 使用,不能打开」的处理办法
    《BERT模型的优化改进方法》读书笔记
    面试突击50:单例模式有几种写法?
    面试突击51:为什么单例一定要加 volatile?
    Spring Cloud OpenFeign 的 5 个优化小技巧!
    WebSocket(SuperSocket.WebSocket实现)服务端主动断开客户端的连接
  • 原文地址:https://www.cnblogs.com/justinli/p/IEnumerable.html
Copyright © 2020-2023  润新知