`

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

阅读更多
这个系列的主要目的是尽量能覆盖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参数,每個迭代器有它自己的一份拷贝。
分享到:
评论
4 楼 ray_linn 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);
        }
3 楼 kulinglei 2009-06-12  
NightTree 写道
lz给深入讲讲委托吧。。。

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


期待中,我只知道他用的是函数指针 c转c#的人
2 楼 NightTree 2009-06-11  
lz给深入讲讲委托吧。。。

java转c#的人,总感觉委托比其他东西难理解些。。。
1 楼 kulinglei 2009-06-10  
积分又50了,该接je的老底了啊

相关推荐

    c#入门经典,语言参考(精)

    C# 2.0版本引入了许多重要的新特性,其中最值得关注的是**泛型**、**匿名方法**、**迭代器**等。 ##### 1. 泛型 泛型是C# 2.0中最重要的新特性之一,它允许开发者编写类型安全的代码,这些代码可以针对任何数据...

    C#API官方手册下载

    例如,IAsyncEnumerable&lt;T&gt;接口提供了异步迭代器,使得异步流式处理成为可能。 九、C#的新特性 随着版本的更新,C#不断引入新的特性和改进。例如,C# 6引入了null条件运算符?.和null合并运算符??;C# 7.0引入了模式...

    c#2.0 宝典 源文件21-25

    9. **类型安全的集合初始化器**:这使得我们可以使用简洁的语法来初始化集合,如`new List&lt;int&gt; { 1, 2, 3 }`。 10. **可空引用类型**:C# 2.0引入了可空引用类型,允许引用类型的值为`null`,增强了代码的健壮性。...

    走进Linq 走进Linq

    编译器在将C#代码转化为MSIL(Microsoft Intermediate Language)时,会识别并转换LINQ查询表达式,将它们转化为标准的迭代器模式或者其他合适的代码结构。这样,原本在SQL中才有的`select`、`from`、`where`、`...

    C# 编程语言详解1(共2个分卷)

    书中前面讲了1.1的语法后面是2.0的泛型,迭代器,匿名方法。没有设计高级知识。

    C# 2005 Programmer's Reference

    迭代器块是C# 2005中的另一个新增特性,它简化了编写迭代器的代码。以前,创建一个枚举器需要实现`IEnumerator`接口,现在只需定义一个返回`IEnumerable&lt;T&gt;`的函数,并使用`yield return`关键字来提供序列中的每个...

    c#2.0 宝典 源文件 1-5

    4. **泛型**:泛型是C# 2.0的一大亮点,它提供了类型安全的容器,如List&lt;T&gt;和Dictionary&lt;TKey,TValue&gt;。泛型使得代码更加通用,提高了效率并减少了类型转换的需要。 压缩包内的源文件01-05,很可能是对这些特性的...

    C#3.0完全自学宝典(C#加入了许多新的特性)

    7. **迭代器**:迭代器使开发者能够自定义迭代过程,如在`yield return`语句中实现。它们常用于实现`IEnumerable&lt;T&gt;`接口,使自定义类型能像数组和列表一样被遍历。 8. **泛型约束**:C# 3.0增强了泛型约束,如`...

    Visual C# 2005编程技巧大全.part1

    此外,C# 2005引入了泛型,这是一种强大的工具,可以创建类型安全且可重用的数据结构,如List&lt;T&gt;和Dictionary&lt;TKey, TValue&gt;。书中的例子可能会展示如何使用泛型来提高代码的灵活性和效率。 在文件名称列表中,我们...

    Programming Microsoft Visual C# 2005 - The Language

    5. **数组与集合**:书中会介绍一维和多维数组,以及.NET框架提供的ArrayList和Generic Collections,如List&lt;T&gt;。 6. **泛型**:C# 2.0引入了泛型,允许创建类型参数化的类、接口和方法,以提高代码的复用性和效率...

    一本讲解c#语言规范的书,写的很详细(第二部分)

    - 在C#中,可以通过在类型声明(如类、接口或方法)中使用尖括号`&lt;&gt;`来定义一个泛型类型。例如: ```csharp public class GenericClass&lt;T&gt; { // 成员 } ``` 使用泛型类型时,需要提供具体的类型参数,称为...

    《Visual C# 2005编程技巧大全》-罗斌-源代码G

    1. **C# 2005基础语法**:C# 2005是.NET Framework 2.0的一部分,引入了许多新的特性和改进,如匿名方法、迭代器、Partial类、Lambda表达式等。G子文件可能包含这些语法的应用实例,帮助读者理解它们的用法和优点。 ...

    professional C#, 3rd edition 书 PDF版本 + 源代码

    书中涵盖了C# 3.0版本的所有关键知识点,包括 LINQ、匿名方法、Lambda表达式、泛型、迭代器以及面向服务的编程等。 1. **C#基础**:书中首先介绍了C#的基础语法,如变量、数据类型、控制流语句(如if、for、while)...

    C#语言规范

    11. 集合与泛型集合:C#提供了丰富的集合类,如List&lt;T&gt;、Dictionary&lt;TKey, TValue&gt;等,这些类都实现了通用接口如IEnumerable&lt;T&gt;,便于进行迭代和操作。 12. 错误处理:C#通过try-catch-finally语句块来处理异常,...

    C#2.0完全自学手册

    13. **类型安全的ArrayList和Hashtable**:C# 2.0提供了List&lt;T&gt;和Dictionary&lt;TKey, TValue&gt;等类型安全的集合,避免了类型转换的麻烦。 通过学习《C#2.0完全自学手册》,你不仅能掌握C# 2.0的基本语法和特性,还能...

    C#完全手册.zip (pdf文档)

    11. **集合与泛型集合**:C#提供了多种内置的集合类,如List&lt;T&gt;、Dictionary&lt;TKey,TValue&gt;等,它们实现了泛型,提供了强类型的安全性和性能优化。 12. **并发和并行编程**:C#支持多线程和任务并行库(TPL),以及...

    C#精髓.PDF

    6. 进阶特性:包括匿名方法、迭代器、匿名类型等较高级的概念。 7. C#在.NET平台的应用:例如使用.NET框架提供的各种命名空间和类库。 8. 与COM技术的互操作性:如何在C#中使用COM组件以及如何将C#组件暴露给COM环境...

    C#2005图书馆管理系统.rar

    1. **C#基础**:C# 2005是.NET Framework 2.0版本的一部分,引入了诸如匿名方法、迭代器和部分类型等新特性。这个系统利用了C#的基础语法,如类、对象、属性、方法等,展示了面向对象编程的基本理念。 2. **Windows...

    Visual C# 2005开发技术

    1. **语法基础**:C#的语法与C++和Java有诸多相似之处,但也有其独特之处。包括基本数据类型(如int、double、bool等)、变量声明、常量、运算符、控制流语句(如if-else、switch-case、for、while等)以及函数的...

Global site tag (gtag.js) - Google Analytics