论坛首页 编程语言技术论坛

C# 语法书 之 <1> 迭代器

浏览 3760 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-06-09   最后修改:2010-04-07
这个系列的主要目的是尽量能覆盖C# 1.1之后的语法更新,以便让大家能够熟悉C# 2.0到4.0的语法特性,以提高编程效率,这里我忽略了一些诸如泛型、LINQ等等需要大章节才能阐述清楚的东西,原因是关注这些知识点的文章比比皆是,我所要写的语法都是一些比较小的,容易被人所忽略的地方。如果我有任何遗漏或者错误的地方,请给我个站内消息


1. 迭代器   适用范围:c# 2.0之后、

C# 1.0开始提供了非常方便的foreach循环,只要一个数据集实现了一个无参数的,返回Enumerator的GetEnumerator方法(甚至不需要实现任何接口),就可以在foreach循环中遍历数据集中的每个元素。大部分情况下,我们将数据存入某个Collection类,利用Collection类的GetEnumerator方法,就可以很方便地使用Foreach循环。

但有些时候我们必须自己去实现Enumerator,比如说打印某年每个月的天数,为了体现OOP的原则,我们的程序逻辑(即对闰年的判断,很高兴C#提供了相关的方法)应该被封装起来。


using System;
using System.Collections;
namespace SharpDemo{

    public class DaysOfTheMonth
    {

        public DaysOfTheMonth(int year)
        {
            this.year = year;
        }      

       int year = 1900;

        public System.Collections.IEnumerator GetEnumerator()
        {
            return new DaysOfMonthEnumrator(this.year);
        }

        class DaysOfMonthEnumrator : IEnumerator
        {
            private int[] days = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

            public DaysOfMonthEnumrator(int year)
            {
                if (DateTime.IsLeapYear(year))
                {
                    days[1] = 29;
                }

            }
            private int index = -1;

            public IEnumerator GetEnumerator()
            {
                return this;
            }

            public void Reset()
            {
                index = -1;
            }

            public object Current
            {
                get
                {
                    if (this.index < this.days.Length)
                    {
                        return this.days[this.index];
                    }
                    else
                    {
                        throw new IndexOutOfRangeException();
                    }
                }
            }

            public bool MoveNext()
            {
                if (this.days.Length == 0)
                {
                    return false;
                }
                else
                {
                    this.index += 1;
                    if (this.index == this.days.Length)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }

    }

  
    }
}


这段代码较长但不难理解,它相当好地封装了程序逻辑,调用起来也相当简洁优雅:

 DaysOfTheMonth enu = new DaysOfTheMonth(1981);            
 foreach( int days in enu)
       Console.Out.WriteLine(days);


我们也看到实现Enumerator的过程未免过于复杂,一旦我们需要多个Enumerator来进行逆序,正序,奇偶数序迭代,代码就会相当繁杂。

C# 2.0之后新的yield关键字使得这个过程变得轻松简单。

    public class DaysOfMonth
    {
        int year = 1900;
        int[] days = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        public DaysOfMonth2(int year)
        {
            this.year = year;
            if (DateTime.IsLeapYear(year))
            {
                days[1] = 29;
            }
        }

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < this.days.Length; i++)
            {
                yield return this.days[i];
            }
        }

    }


这就是C# 2.0 中的迭代器,它使您能够方便地在类或结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口。

迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>。当编译器检测到迭代器时,它将自动生成 IEnumerable 或 IEnumerable<T> 接口的 Current、MoveNext 和 Dispose 方法。

yield 关键字用于指定返回的值。到达 yield return 语句时,会保存当前位置。下次调用迭代器时将从此位置重新开始执行。而yield break则可以中止迭代,下面的代码有助于理解这点。

public class CityCollection : IEnumerable<string>
{
   public IEnumerator<string> GetEnumerator()
   {
      yield return "New York";
      yield return "Paris";
      yield return "London";
   }
}



会依次打印出"New York", "Paris" "London". 有时候迭代器与迭代一起可以产生奇妙的效果,但也会耗费大量的内存。

   IEnumerable<T> ScanInOrder(Node<T> root)
   {
      if(root.LeftNode != null)
      {
         foreach(T item in ScanInOrder(root.LeftNode))
         {
            yield return item;
         }
      }

      yield return root.Item;
 
      if(root.RightNode != null)
      {
         foreach(T item in ScanInOrder(root.RightNode))
         {
            yield return item;
         }
      }
   }




foreach语句会隐式地调用集合的无参的GetEnumerator方法來得到一個迭代器。一个集合类中只能定义一个这样的无参的GetEnumerator方法,不过我们也可以在类中实现多个迭代器,但每个迭代器都必须像任何类成员一样有唯一的名称。

using System.Collections.Generic;
public class Stack<T>: IEnumerable<T>
{
         T[] items;
         int count;
         public void Push(T data) {...}
         public T Pop() {...}
         public IEnumerator<T> GetEnumerator() {
                  for (int i = count – 1; i >= 0; --i) {
                           yield return items[i];
                  }
         }
         public IEnumerable<T> TopToBottom {
                  get {
                           return this;
                  }
         }
         public IEnumerable<T> BottomToTop {
                  get {
                           for (int i = 0; i < count; i++) {
                                    yield return items[i];
                           }
                  }
         }
}



TopToBottom属性的get访问器只返回this,因为Stack本身就是一个可枚举类型。BottomToTop屬性使用C#迭代器返回了另一个可枚举接口。

实现IEnumerable接口的类看起来非常象一个枚举器工厂类,每次都产生一个独立的IEnumerator类。

using System;
using System.Collections.Generic;
class Test
{
         static IEnumerable<int> FromTo(int from, int to) {
                  while (from <= to) yield return from++;
         }
         static void Main() {
                  IEnumerable<int> e = FromTo(1, 10);
                  foreach (int x in e) {
                           foreach (int y in e) {
                                    Console.Write("{0,3} ", x * y);
                           }
                           Console.WriteLine();
                  }
         }
}


上面的代码打印了一个从1到10的乘法表。注意FromTo方法只被调用了一次用来产生实现了IEnumerable接口的变量e。而e.GetEnumerator()被调用了多次(通过foreach语句)來产生多个相同的迭代器。這些迭代器都封裝了FromTo声明中指定的代碼。注意,迭代其代码的时候改变了from参数。但是,迭代器是独立的,因此对于from参数和to参数,每個迭代器有它自己的一份拷贝。
   发表时间:2009-06-10  
积分又50了,该接je的老底了啊
0 请登录后投票
   发表时间:2009-06-11  
lz给深入讲讲委托吧。。。

java转c#的人,总感觉委托比其他东西难理解些。。。
0 请登录后投票
   发表时间:2009-06-12  
NightTree 写道
lz给深入讲讲委托吧。。。

java转c#的人,总感觉委托比其他东西难理解些。。。


期待中,我只知道他用的是函数指针 c转c#的人
0 请登录后投票
   发表时间:2009-06-14  
再给一段我实际工作中的例子,不借助yield return,代码就必须借助IList,其结果是无法干净优雅


        public static IEnumerable<String> GetListOfMountPoints()
        {
            StringBuilder buffer = new StringBuilder((int)N, (int)N);
            IntPtr hVolumn = Volume.FindFirstVolume(buffer, N);
            do
            {
                String volumn = buffer.ToString();
                IntPtr hMountPoint = Volume.FindFirstVolumeMountPoint(volumn, buffer, N);
                do
                {
                    yield return buffer.ToString();
                } while (Volume.FindNextVolumeMountPoint(hMountPoint, buffer, N));
                Volume.FindVolumeMountPointClose(hMountPoint);

            } while (Volume.FindNextVolume(hVolumn, buffer, N));

            Volume.FindVolumeClose(hVolumn);
        }
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics