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";
        }
    }
}

输出结果:
在这里插入图片描述
上面代码的执行过程:

  1. Main调用GetEnmuerableTest()方法。
  2. GetEnmuerableTest()方法会为我们创建一个编译器生成的新的类“Iterator/‘<GetEnmuerableTest()>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更高;


迭代器模式

提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
在这里插入图片描述
迭代类:

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

C#迭代器模式Demo

在这里插入图片描述

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

测试结果:
在这里插入图片描述

参考资料

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

23种设计模式C#

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
原型模式是一种创建型设计模式,其提供了一种复制已有对象的方法来生成新对象的能力,而不必通过实例化的方式来创建对象。原型模式是通过克隆(浅复制或深复制)已有对象来创建新对象的,从而可以避免对象创建时的复杂过程。 在C#中,可以通过实现ICloneable接口来实现原型模式。ICloneable接口定义了Clone方法,该方法用于复制当前对象并返回一个新对象。需要注意的是,Clone方法返回的是Object类型,需要进行强制类型转换才能得到复制后的对象。 以下是一个简单的示例代码: ```csharp public class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } public object Clone() { return MemberwiseClone(); } } // 使用示例 var person1 = new Person { Name = "Tom", Age = 20 }; var person2 = (Person)person1.Clone(); person2.Name = "Jerry"; Console.WriteLine(person1.Name); // 输出 "Tom" Console.WriteLine(person2.Name); // 输出 "Jerry" ``` 在上面的示例代码中,实现了一个Person类,并实现了ICloneable接口中的Clone方法来实现原型模式。复制对象时,使用MemberwiseClone方法进行浅复制,即只复制值类型的字段和引用类型字段的引用,而不复制引用类型字段所引用的对象。在使用示例中,首先创建一个Person对象person1,然后通过Clone方法复制一个新的对象person2,修改person2的Name属性后,输出person1和person2的Name属性,可以看到person1的Name属性并没有改变,说明person2是一个全新的对象。 需要注意的是,如果要实现深复制,即复制引用类型字段所引用的对象,需要在Clone方法中手动将引用类型字段复制一份。另外,使用原型模式时,需要注意复制后的对象和原对象之间的关系,如果复制后的对象修改了原对象的状态,可能会对系统产生意想不到的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值