C# 设计模式———迭代器模式

前言

在正文开始之前先吐槽一下,我发现出来混真的迟早都是要还的。直接上图:

  1. foreach原理 底层原理没细究
  2. 学习游戏开发-> Unity->协程->迭代器 底层原理吓跑我
  3. 学习游戏开发->设计模式->迭代器模式 来吧,这次不跑了

迭代器

什么场景下需要使用迭代器?
假设有一个数据容器(可能是Array List Tree等),对使用者来说,显然希望这个数据容器能够提供一种无须了解它的内部实现就可以获取其元素的方法,无论它是Array还是List,都希望可以通过相同的方法达到我们的目的。

迭代器模式它通过持有迭代状态追踪当前元素,并且识别下一个需要被迭代的元素,从而可让用户通过使用特定的界面巡防容器中的每一个元素而无需了解底层的实现。

在C#中,迭代器被封装在
IEnumerable IEnumerator IEnumerable IEnumerator

IEnumerable非泛型形式

public interface IEnumerable
{
     IEnumerator GetEnumerator();
}

IEnumerator非泛型形式

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

IEnumerable泛型形式

public interface IEnumerable<out T> : IEnumerable
{
   IEnumerator<T> GetEnumerator();
   IEnumerator GetEnumerator();
}

IEnumerator泛型形式

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
   void Dispose();
   Object Current {get;}
   T Current{get;}
   bool MoveNext();
}

public interface IDisposable
{
   void Dispose();
}

IEnumerable 接口定义了一个可以获取IEnumerator的方法——GetEnumerator。而在IEnumerator则在目标序列上实现循环迭代(使用MoveNext()方法,以及Current属性来实现),直到你不再需要任何数据或者没有数据可以被返回。使用这个接口,可以保证我们实现常见的foreach循环。

Q :
为什么会有2个接口呢?为何IEnumerable自己不直接实现MoveNext()方法,提供Current属性,而是使用额外的一个接口IEnumerator来专门处理?
A :
假设有两个不同的迭代器要对同一个序列进行迭代,则需要保证这两个独立的迭代状态能够被正确地保存和处理。这也是IEnumerator要做的。而为了不违背单一职责原则,不使IEnumerable拥有过多职责从而陷入分工不明的窘境,所以IEnumerable自己并没有实现MoveNext()方法。


迭代器的执行步骤

using System;
using System.Collections.Generic;

namespace Iterator
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (string s in GetEnumerableTest() )
            {
                Console.WriteLine(s);
            }
        }

        static IEnumerable<string> GetEnumerableTest()
        {
            yield return "begin";

            for (int i = 0; i < 10; i++)
            {
                yield return i.ToString();
            }

            yield return "end";
        }
    }
}

输出结果:
C# 设计模式———迭代器模式_第1张图片
上面代码的执行过程:

  1. Main调用GetEnmuerableTest()方法。
  2. GetEnmuerableTest()方法会为我们创建一个编译器生成的新的类“Iterator/‘c_Iterator’”的实例。此时GetEnmuerableTest()方法中的代码尚未执行。
  3. Main调用MoveNext()方法。
  4. 迭代器开始执行,直到它遇到第一个yield return语句。此时迭代器会获取当前的值是“begin”并且返回true以告知此时还有数据。
  5. Main使用Current属性以获取数据,并打印出来。
  6. Main再次调用MoveNext()方法。
  7. 迭代器继续从上次遇到yield return的地方开始执行,并且和之前一样,直到遇到下一个yield return语句。
  8. 迭代器按照这种方式循环,直到MoveNext()方法返回false,以告知此时已经没有数据了。

Hints:

  • 在第一次调用MoveNext()方法之前,我们自己在GetEnmuerableTest()中的代码不会执行。
  • 之后调用MoveNext()方法时,会从上次暂停(yield return)的地方开始。
  • 编译器会保证GetEnmuerableTest()方法中的局部变量能够被保留,换句话说,虽然本例中的i是值类型实例,但是它的值其实是被迭代器保存在堆上的,这样才能保证每次调用MoveNext()时,它是可用的。这也是说明迭代器块中的局部变量会被分配在堆上的原因。

依靠状态机实现迭代器


foreach

foreach( var item in list)
{
    Console.WriteLine(item);
}
var enumerator = list.GetEnumerator();
while(enumerator.MoveNext() )
{
    Console.WriteLine(enumerator.Current);
}

注意foreach的效率比for更高;


迭代器模式

提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
C# 设计模式———迭代器模式_第2张图片
迭代类:

  1. 读写分离;
  2. 封装了元数据:比如底层的array数组;
  3. 简化的业务逻辑;
  4. 迭代器简化了聚合类。

C#迭代器模式Demo

C# 设计模式———迭代器模式_第3张图片

using System;
using System.Collections.Generic;

namespace IteratorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            Aggreation aggreation = new Aggreation();
            aggreation.Add(0);
            aggreation.Add(10);
            aggreation.Add(12);

            var enumerator = aggreation.GetEnumerable();

            while (enumerator.MoveNext())
            {
                Console.WriteLine(enumerator.Current);
            }
        }
    }

    public interface IMyEnumerable
    {
        int Current { get; }
        bool MoveNext();
    }

    public class MyEnumerable : IMyEnumerable
    {
        public int Current { get; set; }
        private int index = 0;
        private int current = 0;
        private Aggreation aggreation = new Aggreation();

        public MyEnumerable(Aggreation aggreation)
        {
            this.aggreation = aggreation;
        }

        public bool MoveNext()
        {
            if(index < aggreation.length)
            {
                this.Current = aggreation[index];
                index++;
                return true;
            }
            return false;
        }
    }

    public class Aggreation
    {
        private List<int> list = new List<int>();
        public MyEnumerable GetEnumerable()
        {
            return new MyEnumerable(this);
        }

        public void Add(int num)
        {
            list.Add(num);
        }

        public int this[int index]
        {
            get
            {
                return list[index];
            }
        }

        public int length
        {
            get { return list.Count; }
        }
    }
}

测试结果:
C# 设计模式———迭代器模式_第4张图片

参考资料

  1. Unity3D脚本编程 陈嘉栋 著
  2. https://www.bilibili.com/video/av78515440?p=4
  3. 更多:

23种设计模式C#

你可能感兴趣的:(C#,设计模式,设计模式)