zoukankan      html  css  js  c++  java
  • C#迭代器

    1. 概述

    迭代器用于遍历集合。迭代器可定义为方法或get访问器。在event, 实例构造函数,静态构造函数以及静态析构函数中不能使用迭代器。

    yield 关键字专门为迭代器而设计。通过 yield定义迭代器,在实现IEnumerable 和 IEnumerator 接口以自定义集合时无需添加其他显式类(保存枚举状态)。

    yield 语句有两种形式:

    yield return <expression>;
    yield break;

    yield return 语句一次返回一个元素:foreach 语句或LINQ查询每次迭代都会调用对应迭代方法,该迭代方法运行到 yield return 语句时,会返回一个expression,并保留当前的运行位置,下次调用迭代器函数时直接从该位置开始。

    yield break 语句用于终止迭代。

    迭代器方法和get访问器

    迭代器的声明必须满足以下条件:

    • 返回类型必须为IEnumerable, IEnumerable<T>IEnumerator<T>.
    • 声明中不能有ref或out参数。

    返回IEnumerable或IEnumerator的迭代器,其yield类型为object。如果迭代器返回的类型为IEnumerable<T>或IEnumerator<T>,则必须把yield return语句的表达式类型隐式转换为泛型类型参数的类型。

    具有以下特点的方法不能包含yield returnyield break语句:

    • 匿名方法。
    • 包含unsafe块的方法。

    异常处理

    不能将yield return语句放在try-catch块中,但可以放在try-finally语句的try块中。

    yield break语句可放在try块或catch块中,但不能放在finally块中。

    如果foreach语句(迭代器之外)发生异常,将执行迭代器的finally块。

    实现

    虽然我们以方法的形式定义迭代器,但是编译器会将其转换为嵌套类。该类会对迭代器的位置进行了记录。

    在为类创建迭代器时,不用完全实现IEnumerator接口。当编译器检测到迭代器时,会自动为生成IEnumerator或IEnumerator<T>接口的Current, MoveNext以及Dispose方法。

    迭代器不支持IEnumerator.Reset方法,要重新遍历,必须获取一个新的迭代器。

    下面代码先从一个迭代器返回IEnumerable<string>,然后遍历其元素:

    IEnumerable<string> elements = MyIteratorMethod();
    foreach (string element in elements)
    {
       …
    }

    调用MyIteratorMethod时不执行实际操作,在foreach循环时,为elements调用MoveNext方法,才真正执行遍历操作,直至下一个yield return 语句。

    在foreach循环的每个后续迭代中,迭代器主体的执行将从它暂停的位置继续,直至到达yield return语句后才会停止。在到达迭代器方法的结尾或yield break语句时,foreach循环完成。

    2. 示例

    public class PowersOf2
    {
        static void Main()
        {
            // Display powers of 2 up to the exponent of 8:
            foreach (int i in Power(2, 8))
            {
                Console.Write("{0} ", i);
            }
        }
    
        public static System.Collections.IEnumerable<int> Power(int number, int exponent)
        {
            int result = 1;
    
            for (int i = 0; i < exponent; i++)
            {
                result = result * number;
                yield return result;
            }
        }
    
        // Output: 2 4 8 16 32 64 128 256
    }

    上例中,for循环包含一个yield return语句。Main中的foreach循环每次迭代都会调用Power迭代器函数。对迭代器函数的每次调用都会从上次结束的地方开始。

    public static class GalaxyClass
    {
        public static void ShowGalaxies()
        {
            var theGalaxies = new Galaxies();
            foreach (Galaxy theGalaxy in theGalaxies.NextGalaxy)
            {
                Debug.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears.ToString());
            }
        }
    
        public class Galaxies
        {
    
            public System.Collections.Generic.IEnumerable<Galaxy> NextGalaxy
            {
                get
                {
                    yield return new Galaxy { Name = "Tadpole", MegaLightYears = 400 };
                    yield return new Galaxy { Name = "Pinwheel", MegaLightYears = 25 };
                    yield return new Galaxy { Name = "Milky Way", MegaLightYears = 0 };
                    yield return new Galaxy { Name = "Andromeda", MegaLightYears = 3 };
                }
            }
    
        }
    
        public class Galaxy
        {
            public String Name { get; set; }
            public int MegaLightYears { get; set; }
        }
    }
    上例对get访问器形式的迭代器进行了演示,在该示例中,每个yield return语句返回一个用户自定义类的实例。

    2. 创建集合类

    在例中,DaysOfTheWeek 类实现了IEnumerable接口,即提供GetEnumerator方法。在迭代DaysOfTheWeek集合类时,编译器会隐式调用GetEnumerator方法,得到IEnumerator。GetEnumerator方法通过yield return语句每次返回一个字符串。

    static void Main()
    {
        DaysOfTheWeek days = new DaysOfTheWeek();
    
        foreach (string day in days)
        {
            Console.Write(day + " ");
        }
        // Output: Sun Mon Tue Wed Thu Fri Sat
        Console.ReadKey();
    }
    
    public class DaysOfTheWeek : IEnumerable
    {
        private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
    
        public IEnumerator GetEnumerator()
        {
            for (int index = 0; index < days.Length; index++)
            {
                // Yield each day of the week.
                yield return days[index];
            }
        }
    }

    3. 泛型迭代器

    static void Main()
    {
        Stack<int> theStack = new Stack<int>();
    
        //  Add items to the stack.
        for (int number = 0; number <= 9; number++)
        {
            theStack.Push(number);
        }
    
        // Retrieve items from the stack.
        // foreach is allowed because theStack implements
        // IEnumerable<int>.
        foreach (int number in theStack)
        {
            Console.Write("{0} ", number);
        }
        Console.WriteLine();
        // Output: 9 8 7 6 5 4 3 2 1 0
    
        // foreach is allowed, because theStack.TopToBottom
        // returns IEnumerable(Of Integer).
        foreach (int number in theStack.TopToBottom)
        {
            Console.Write("{0} ", number);
        }
        Console.WriteLine();
        // Output: 9 8 7 6 5 4 3 2 1 0
    
        foreach (int number in theStack.BottomToTop)
        {
            Console.Write("{0} ", number);
        }
        Console.WriteLine();
        // Output: 0 1 2 3 4 5 6 7 8 9
    
        foreach (int number in theStack.TopN(7))
        {
            Console.Write("{0} ", number);
        }
        Console.WriteLine();
        // Output: 9 8 7 6 5 4 3
    
        Console.ReadKey();
    }
    
    public class Stack<T> : IEnumerable<T>
    {
        private T[] values = new T[100];
        private int top = 0;
    
        public void Push(T t)
        {
            values[top] = t;
            top++;
        }
        public T Pop()
        {
            top--;
            return values[top];
        }
    
        // This method implements the GetEnumerator method. It allows
        // an instance of the class to be used in a foreach statement.
        public IEnumerator<T> GetEnumerator()
        {
            for (int index = top - 1; index >= 0; index--)
            {
                yield return values[index];
            }
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        public IEnumerable<T> TopToBottom
        {
            get { return this; }
        }
    
        public IEnumerable<T> BottomToTop
        {
            get
            {
                for (int index = 0; index <= top - 1; index++)
                {
                    yield return values[index];
                }
            }
        }
    
        public IEnumerable<T> TopN(int itemsFromTop)
        {
            // Return less than itemsFromTop if necessary.
            int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;
    
            for (int index = top - 1; index >= startIndex; index--)
            {
                yield return values[index];
            }
        }
    
    }

    在上面的例子中,Stack<T>泛型类实现了IEnumerable<T>泛型接口。Push方法将T类型值添加到数组,GetEnumerator方法通过yield return语句包含数组值。

    除了泛型的GetEnumerator方法,还必须实现非泛型的GetEnumerator方法。因为IEnumerable<T>从IEnumerable继承而来。非泛型直接通过泛型实现。

    该示例使用命名迭代器以支持对同一集合的多种迭代方式。命名迭代器包括TopToBottom,BottomToTop以及TopN方法。

    其中,BottomToTop属性在get访问器中使用了迭代器。

  • 相关阅读:
    C++ 归纳复习常规篇
    小技巧:linux启动nginx服务异常 systemctl daemon-reload
    每日日报
    每日日报
    每日日报
    每日日报
    每日日报
    每日日报
    Migration in EF 6 Code-First
    国外.net资源学习网站
  • 原文地址:https://www.cnblogs.com/jiawei-whu/p/4350760.html
Copyright © 2011-2022 走看看