C#学习总结-迭代器

概念

迭代器模式提供了一种方法顺序访问一个聚合对象(理解为集合对象)中各个元素,而又无需暴露该对象的内部表示。

迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是简化对象间通讯的模式。

在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,

内建了对迭代器的支持,编译器会将foreach编译来调用GetEnumerator和MoveNext方法以及Current属性,如果对象实现了IDisposable接口,在迭代完成之后会释放迭代器。

迭代器是实现IEnumerator接口的对象,类似数据库中的游标,他是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。(百度百科)

迭代器的执行过程

 为了让大家更好的理解迭代器,下面列出迭代器的执行流程:

C#2中实现GetEnumerator方法的完整代码:
public IEnumerator GetEnumerator()
{
    for (int index = 0; index < this.values.Length; index++)
    {
        yield return values[(index + startingPoint) % values.Length];
    }
}

使用了yield return。这条语句告诉编译器不是普通的方法,而是一个需要执行的迭代块(yield block),他返回一个IEnumerator对象

创建了一个实现了状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量,包括参数。他将所有需要记录的状态保存为实例变量。下面来看看,为了实现一个迭代器,这个状态机需要按顺序执行的操作:

  • 它需要一些初始的状态
  • 当MoveNext被调用时,他需要执行GetEnumerator方法中的代码来准备下一个待返回的数据。
  • 当调用Current属性是,需要返回yielded的值。
  • 需要知道什么时候迭代结束是,MoveNext会返回false

注意:

  • 直到第一次调用MoveNext,GetEnumerator()方法才被调用。
  • 在调用MoveNext的时候,已经做好了所有操作,返回Current属性并没有执行任何代码。
  • 代码在yield return之后就停止执行,等待下一次调用MoveNext方法的时候继续执行。
  • 在方法中可以有多个yield return语句。
  • 在最后一个yield return执行完成后,代码并没有终止。调用MoveNext返回false使得方法结束。

迭代器执行中的特殊行为:

  • 在MoveNext方法第一次执行之前,Current属性总是返回迭代器返回类型的默认的值。例如IEnumeratble返回的是Int32类型,那么默认初始值是0,所以在调用MoveNext方法之前调用Current属性就会返回0。
  • MoveNext方法返回false后,Current属性总是返回最后迭代的那个值。
  • Reset方法一般会抛出异常,而在本文开始代码中,我们手动实现一个迭代器时在Reset中能够正确执行逻辑。
  • 编译器为我们产生的嵌套类会同时实现IEnumerator的泛型和非泛型版本(恰当的时候还会实现IEnumerable的泛型和非泛型版本).

类型:

编译器遇到迭代块(yield block),他返回一个IEnumerator对象,你能够使用迭代块来执行迭代方法并返回一个IEnumerable需要实现的类型,IEnumerator或者对应的泛型。如果实现的是非泛型版本的接口,迭代块返的yield type是Object类型,否则返回的是相应的泛型类型。例如,如果方法实现IEnumerable<String>接口,那么yield返回的类型就是String类型。 在迭代块中除了yield return外,不允许出现普通的return语句。块中的所有yield return 语句必须返回和块的最后返回类型兼容的类型。

Finally语句块的执行

    通常,finally语句块在当方法执行退出特定区域时就会执行。迭代块中的finally语句和普通方法中的finally语句块不一样。就像我们看到的,yield return语句停止了方法的执行,而不是退出方法,根据这一逻辑,在这种情况下,finally语句块中的语句不会执行。

    但当碰到yield break语句的时候,就会执行finally 语句块,这根普通方法中的return一样。一般在迭代块中使用finally语句来释放资源,就像使用using语句一样。

其他

 C#2.0 利用迭代器可以简单实现 GetEnumerator() 函数。迭代器是用于返回相同类型的值的有序集合的一段代码。利用 yield 关键字,实现控制权的传递和循环变量的暂存,使类或结构支持 foreach 迭代,而不必显式实现 IEnumerable 或 IEnumerator 接口,由 JIT 编译器辅助编译成实现了 IEnumerable 或 IEnumerator 接口的对象。yield return 提供了迭代器一个重要功能,即取到一个数据后马上返回该数据,不需要全部数据加载完毕,有效提高遍历效率(延迟加载)。 
 - yield 关键字用于指定返回的值,yield return 语句依次返回每个元素,yield break 语句终止迭代; 
 - 到达 yield return 语句时,保存当前位置,下次调用迭代器时直接从当前位置继续执行; 
 - 迭代器可以用作方法、运算符或get访问器的主体实现,yield 语句不能出现在匿名方法中; 
 - 迭代器返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>; 
 

总结:

使用foreach必须实现IEnumerable,因为要获得迭代器,获得迭代器就必须实现IEnumerable接口中的GetEnumerator()方法

C#1.0 实现迭代器就必须实现IEnumerator接口中的bool MoveNext()和void Reset()方法

C#2.0  使用yield关键字  实质:编译器生成中间代码时为我们生成了一个IEnumerator接口的对象,

实质:foreach被编译后会调用GetEnumerator来返回一个迭代器,也就是一个集合中的初始位置。

 因为迭代的主体在MoveNext()中实现(因为在MoveNext()方法中访问了集合中的当前位置的元素),Foreach中每次遍历执行到in的时候才会调用MoveNext()方法,所以迭代器可以延迟计算

1  编译器遇到yield return会生成IEnumerator接口迭代器对象 ,所以迭代块的代码在迭代器对象里面,延迟到第一次moveNext()。

2  Yield Return关键字的作用就是退出当前函数,并且会保存当前函数执行到什么地方,也就上下文。 (实质)编译器会生成一个状态机来维护迭代器的状态。

return   作用 1调用返回结果 2终止方法执行

yield return 短暂退出方法 等待下一次nextMove()执行

yield break 能够马上终止迭代 下次调用MoveNext()时直接返回false

yield return 后 finally方法不会执行

实现:

C#1.0迭代器的实现


    class Program
    {
        static void Main(string[] args)
        {
            Friends friendcollection = new Friends();
            foreach (Friend f in friendcollection)
            {
                Console.WriteLine(f.Name);
            }

            Console.Read();
        }
    }

    /// <summary>
    ///  朋友类
    /// </summary>
    public class Friend
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public Friend(string name)
        {
            this.name = name;
        }
    }

    /// <summary>
    ///   朋友集合
    /// </summary>
    public class Friends : IEnumerable
    {
        private Friend[] friendarray;

        public Friends()
        {
            friendarray = new Friend[]
            {
                new Friend("张三"),
                new Friend("李四"),
                new Friend("王五")
            };
        }

        // 索引器
        public Friend this[int index]
        {
            get { return friendarray[index]; }
        }

        public int Count
        {
            get { return friendarray.Length; }
        }

        // 实现IEnumerable<T>接口方法
       public  IEnumerator GetEnumerator()
        {
            return new FriendIterator(this);
        }
    }

    /// <summary>
    ///  自定义迭代器,必须实现 IEnumerator接口
    /// </summary>
    public class FriendIterator : IEnumerator
    {
        private readonly Friends friends;
        private int index;
        private Friend current;
        internal FriendIterator(Friends friendcollection)
        {
            this.friends = friendcollection;
            index = 0;
        }

        #region 实现IEnumerator接口中的方法
        public object Current
        {
            get
            {
                return this.current;
            }
        }

        public bool MoveNext()
        {
            if (index + 1 > friends.Count)
            {
                return false;
            }
            else
            {
                this.current = friends[index];
                index++;
                return true;
            }
        }

        public void Reset()
        {
            index = 0;
        }

        #endregion 
    }

C#1.0 当前代码实现步骤:

聚合对象,1 实现IEnumerable的GetEnumerator()方法返回IEnumerator接口的实现,提供迭代器对象

                   2 创建私用数组存储数据  3  提供索引器获取数组元素 4 提供数组长度

迭代器对象,1 私用变量3个,聚合对象,索引下标,当前聚合元素 2构造函数,初始化数据 

                     3 MoveNext() ,返回是否含有数据,并把当前当前聚合元素赋值,下标加1

                      4 提供Current属性返回当前元素,Reset方法重置。

C#2.0 使用yield return语句简化了迭代器的实现

 public IEnumerator GetEnumerator()
        {
            for (int index = 0; index < friendarray.Length; index++)
            {
                // 这样就不需要额外定义一个FriendIterator迭代器来实现IEnumerator
                // 在C# 2.0中只需要使用下面语句就可以实现一个迭代器
                yield return friendarray[index];
            }
        }

1 实现IEnumerable的GetEnumerator()方法返回IEnumerator接口的实现,提供迭代器对象

2 使用yield return直接返回迭代器对象

情景

1  编译器遇到yield return的迭代块(yield block)生成包含状态机的内部类,延迟到第一次执行moveNext()执行

public static IEnumerable<int> WithIterator()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);
                if (i > 1)
                {
                    yield return i;
                }
            }
        }
static void Main(string[] args)
 {
            WithIterator();
 }

运行结果:

无输出。

2  Yield Return关键字的作用就是退出当前函数,并且会保存当前函数执行到什么地方。

 static void Main(string[] args)
        {
              // 测试三
            foreach (int j in WithIterator())
            {
                Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);
            }
        }

运行结果:

应用:

1遍历日期

public IEnumerable<DateTime> DateRange
{
    get
    {
        for (DateTime day=StartDate ; day < =EndDate; day=day.AddDays(1))
        {
            yield return day;
        }
    } 
}

2 遍历文件行

static IEnumerable<String> ReadLines(String fileName)
{
    using (TextReader reader = File.OpenText(fileName))
    {
        String line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

 3

public delegate TResult Func<TResult>()

代理没有参数,返回和类型参数相同的类型。我们想获得TextReader对象,所以可以使用Func<TextReader>,代码如下:
using (TextReader reader=provider())
{
    String line;
    while ((line=reader.ReadLine())!=null)
    {
        yield return line;
    }         
}

4 实现where 

异常判断:将方法分为两部分,一部分像普通方法那样对参数进行验证,另一部分代码使用迭代块对主体逻辑数据进行惰性处理。

每一次只在内存中请求一行并对其进行处理。

public static IEnumerable<T> Where<T>(IEnumerable<T> source, Predicate<T> predicate)
{
    if (source == null || predicate == null)
        throw new ArgumentNullException();
    return WhereImpl(source, predicate);
}

private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source, Predicate<T> predicate)
{
    foreach (T item in source)
    {
        if (predicate(item))
            yield return item;
    }
}

扩展:

Yield Return是可以让当前函数的进程状态切换到阻塞状态,然后去选择了把cpu交给当前的出进程,这样就转而执行调用方函数。 (补充个小知识点其实我们写的程序加入到内存中,并不定就是一个进程,我们会根据情况分成几个子进程去干活,方便操作系统去管理以及多道程序运行在内存,提高计算机资源的利用率) 

编译器会生成一个状态机来维护迭代器的状态。

当希望获取一个IEnumerable<T>类型的集合,而不想把数据一次性加载到内存,就可以考虑使用yield return实现"按需供给"。

参考文献:

https://www.cnblogs.com/zhili/archive/2012/12/02/Interator.html

https://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html

猜你喜欢

转载自blog.csdn.net/qq_25744257/article/details/85294222
今日推荐