C#2.0-迭代器应用

转自: http://www.cnblogs.com/bluefee

[翻译+补充说明] 
原文参考:http://www.theserverside.net/articles/showarticle.tss?id=IteratorsWithC2
注释:对于Enumerable和Enumerator的翻译可能不太贴切
可以参考以下词汇:枚举 枚举构造器


介绍
 
迭代器作为匿名方法存在是很不明显的,但是其强大的特性现在被引入到Csharp2.0版本中了。迭代器有资格作为语法构造的一部分出现并不意味着从.NET 1.x过渡到2.0版本后通用语言运行时与.NET中间语言指令集发生了改变。只是如同匿名方法一样,所有的这些魔法都隐藏到编译器中了。为了理解下面的CSharp2.0中迭代器的原理,我们将深入到其内部中,并适当的运用一些在2.0版本中出现的基本特性,接着我们还将分析其中编译器的内部工作机制,最后我们还将掌握在CSharp2.0使用迭代器的一些高级技术。
 
Csharp1.x 中迭代器,枚举,枚举器以及使用迭代设计模式
 
   在我们分析CSharp1和CSharp2的枚举器之前。我们有必要知道什么是可枚举的以及什么是枚举器。我们在谈及一个对象是可枚举的是指如果一个对象包含或引用了一个对象集和如果一个元素属于这个集合那么我们能够使用CSsharp关键字’ foreach列举出来。实现了System.Collections.IEnumerable接口的实例化的类都是可枚举的。下面我们将会看到,具有某些可枚举特征并不意味着一定满足这个条件。
 

如果一个类实现了System.Collections.IEnumerable接口那么它就是一个枚举器。这里是一些接口的定义:

namespace  System.Collections
{
//为接口绑定属性
[System.Runtime.InteropServices.GuidAttribute(“496B0ABE-CDEE-11d3-88E8-00902754C43A”)]
//注意这里是为了运行时反射
//我们看看该配件的自描述
//使用了GuidAttribute类,这里我使用了Reflector查看了该类的实现
//该类是这样描述的
//[AttributeUsage(AttributeTargets.Delegate | (AttributeTargets.Interface | (AttributeTargets.Enum | (AttributeTargets.Struct | (AttributeTargets.Class | AttributeTargets.Assembly)))), Inherited=false)]
//ArrtibuteTargets是枚举类型
//指出该类绑定了代理,接口,枚举,结构,类以及配件,并且不可继承
//这里的继承含义不是类的派生,而是指出属性不可继承,这意味假设存在派生类并对其类重新进行了属性绑定,那么派生类所绑定的属性将覆盖基类所绑定的属性
//不过,该类同时也是一个不可继承的类,这在类的定义中可以看到含有一个sealed修饰符

public interface IEnumerable
{
    [System.Runtime.InteropService.DispIdAttribute(
252)]
    System.Collections.IEumerator GetEnumerator();
}


//为接口绑定属性
[System.Runtime.InteropServices.GuidAttribute("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEumerator
{
    
//返回当前集合中的元素
    object Current
    
{
        
get;
    }

    
//返回值true表示迭代器成功前进到集合中的下一个元素
    
//返回值false表示已经位于集合的末尾
    bool MoveNext();
    
//恢复初始化指向的位置
    void Reset();
}

}


客户端调用IEnumerable.GetEnumerator()方法实际上是通过IEnumeratorCurrent属性,该属性在派生类EnumeratorConcret实现,并实例化为EnumeratorConcret。当MoveNext()Reset()方法被调用的时候,枚举器将保持当前的索引,这样客户端就可以通过Current属性访问集合中的元素。它的UML视图看起来像这样:

对于那些知晓GoF设计模式的人来说,CSharp迭代器的实现是显而易见的,这个设计模式被命名为迭代;
 
一个例子

    这有一个使用CSharp语法实现迭代的例子。Person类是一个可枚举的类。PersonsEnumerator类是一个枚举器类。注意如果类实现了以上两种接口那么Person类就具有了可枚举和枚举器两种身份。无论怎样,我们将看到一种更优雅的实现类的方法(这里我们遵循了一条重要的面向对象原则:创建一个可复用的类【对象】)

using  System;
using  System.Collections;
public  class  Persons : IEnumerable
{
    
private class PersonsEnumerator : IEnumerator
    
{
        
private int index = -1;
        
private Persons P;
        
public PersonsEnumerator(Persons P)
        
{
            
this.P = P;
        }

        
public bool MoveNext()
        
{
            index
++;
            
return P.m_Names.Length;
        }

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

        
public object Current
        
{
            
get
            
{
                
return P.m_Names[index];
            }

        }

    }

    
    
string[] m_Names;
    
public Persons(params string[] Names)
    
{
        m_Names 
= new string[Names.Length];
        Names.CopyTo(m_Names,
0);
    }

    
private string this[int index]
    
{
        
get
        
{
            
return m_Names[index];
        }

        
set
        
{
            m_Names[index] 
= value;
        }

    }

}


class  Program
{
    
static void Main(string[] args)
    
{
        Persons arrPersons 
= new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
        
foreach (string s in arrPersons)
        
{
            Console.WriteLine(s);
            Console.ReadLine();
        }

    }

}

 


该程序将输出:
Michel
Christine
Mathieu
Julien
这段代码中的大部分语句用来实现枚举器的索引行为,我们更希望看到 CSharp2.0 达到同样功能却戏剧性的缩小的代码数量。
注意:C#编译器类似如下代码来解释foreach关键字

 

class  Program
{
    
static void Main(string[] args)
    
{
          Persons arrPersons 
= new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
          IEnumerator e 
= arrPersons.GetEnumerator();
          While (e.MoveNext())
          
{
       Console.WriteLine((
string)e.Current);
       Console.ReadLine();
          }

    }

}

 


单一枚举包含多个枚举构造器
    一个可枚举类不一定要服从实现 IEnumerable 接口。这个责任可以被委托到其他类中实现。例如:

 

using  System;
    
using  System.Collections;
    
public  class  Persons     // 这里没有实现IEnumerable接口
     {
        
private class PersonsEnumerator :IEnumerator
        
{
            …
        }

        
private class PersonsEnumerable :IEnumerable
        
{
            
private Persons m_Persons;
            inernal PersonEnumerable(Persons persons)
            
{
                m_Persons 
= persons;
            }

            IEnumerator IEnumerable.GetEnumerator()
            
{
                
return new PersonsEnumerator(m_Persons);
            }

        }

        
public IEnumerable InOrder
        
{
            
get
            
{
                
return new PersonsEnumerable(this);
            }

        }

    }

class  Program
{
    
static void Main(string[] args)
    
{
        Persons arrPersons 
= new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
        
foreach (string s in arrPersons.InOrder)
        
{
            Console.WriteLine(s);
            Console.ReadLine();
        }

    }

}


这个方法非常方便的在同一个可枚举类中实现了多个枚举构造器类。一个反序的枚举器(元素字段以反序排列)或一个具有EvenPosOnly字段的用于连续排列元素的枚举器会更加有用。

CSharp 1.0
迭代器的缺点

    无疑CSharp1.0迭代器语法对于它所提供的功能来说显得太笨拙了。处出于这个原因,大多数开发者不愿意使用它。此外对这种语法而言,尝试枚举一些特殊的集合(象这种二叉树)那么很快将成为一场恶梦。

关键字yield return

C#2.0通过强大的yield return关键字无缝实现了迭代器模式。特别是减轻了开发者实现一个枚举器和可枚举类的负担。这里我重写了一个前面的例子:

using  System;
using  System.Collections;
public  class  Persons : IEnumerable
{
    
string[] m_Names;
    
public Persons(params string[] Names)
    
{
        m_Names 
= new string[Names.Length];
        Names.CopyTo(m_Names,
0);
    }

    
public IEnumerator GetEnumerator()
    
{
        
foreach (string s in m_Names)
        
{
            yield 
return s;
        }

    }

}

class  Program
{
    
static void Main(string[] args)
    
{
        Persons arrPersons 
= new Persons(“Michel”,”Christine”,”Mathieu”,”Julien”);
        
foreach (string s in arrPersons)
        
{
            Console.WriteLine(s);
            Console.ReadLine();
        }

    }

}


别被 yield return关键字的行为所困扰。Yield return关键字看起来像是返回了一个字符串但是实际上是通过调用GetEnumerator()方法返回了Ienumerable这个对象。既然我们不能明确的提供这样的实现,那么该由那个类实现IEnumerator的返回呢。本文将对此展开讨论并深入到Csharp2.0迭代器基本原理揭示这些神秘的东西。

注意:方法能对yield return关键字多次调用.实例如下:

using  System; 
using  System.Collections; 
public  class  Persons : IEnumerable 

    
string[] m_Names; 
    
public Persons(params string[] Names) 
    

        m_Names 
= new string[Names.Length]; 
        Names.CopyTo(m_Names,
0); 
    }
 

    
public IEnumerator GetEnumerator() 
    

        yield 
return "Michel"
        yield 
return "Chirstine"
        yield 
return "Mathieu"
        yield 
return "Julien"
    }
 
}
 
class  Program 

    
static void Main(string[] args) 
    

        Persons arrPersons 
= new Persons("Michel","Christine","Mathieu","Julien"); 
        
foreach (string s in arrPersons) 
        

            Console.WriteLine(s); 
            Console.ReadLine(); 
        }
 
    }
 
}
 



这段程序将同样产生以下输出

       Michel
      Christine
      Mathieu 
      Julien

范型和迭代器

.NET Framework 2.0IEnumerableIEnumerator接口都提供了一种范型形态。

 

namespace  System.Collections.Generic
{
    [System.Runtime.InteropServices.ComVisibleAttribute(
false)]
    [Systen,CLSCompliantAttribute(
false)]
    
public interface IEnumerable<T>
{
System.Collections.Generic.IEnumerator
<T> GetEnumerator();
}

[System.Runtime.InteropServices.ComVisibleAttribute(
false)]
[Systen,CLSCompliantAttribute(
false)]
public interface IEnumerator<T> : System.Idisposable
{
    T Current
    
{
        
get;
    }

    
bool MoveNext();
}

}

 

 

 

我们注意到IEnumerator接口实现了IDisposable接口,同时还注意到IEnumerator.Reset()方法已经被去掉了。现在我们能告诉编译器它枚举的是一个字符串集合而不是一个对象集合。

 

using  System;
using  Systen,Collections.Generic;
public  class  Persons : IEnumerable < string >
{
    
string[] m_Names;
    
public Persons(params string[] Names)
    
{
        m_Names 
= new string[Names.Length];
        Names.CopyTo(m_Names,
0);
    }

    
public IEnumerator<string> GetEnumerator()
    
{
        
foreach (string s in m_Names)
            yield 
return s;
    }

}


 


单一枚举包含多个枚举构造器

 

using  System;
using  Systen,Collections.Generic;
public  class  Persons : IEnumerable < string >
{
    
string[] m_Names;
    
public Persons(params string[] Names)
    
{
        m_Names 
= new string[Names.Length];
        Names.CopyTo(m_Names,
0);
    }

    
public IEnumerable<string> Reverse
    
{
        
get
        
{
            
for (int i = m_Names.Length - 1; i >= 0; i--)
                yield 
return m_Names[i];
        }

    }

    
public IEnumerable<string> PositionsPaires
    
{
        
get
        
{
            
for (int i= 0; i<= m_Names.Length; i++,i++)
                yield 
return m_Names[i];
        }

    }

    
public IEnumerable<string> Concat
    
{
        
get
        
{
            
foreach (string s in Reverse)
                yield 
return s;
            
foreach (string s in PositionsPaires)
                yield 
return s;
        }

    }

}

class  Program
{
    
static void Main (string[] args)
    
{
Persons arrPersons 
= new Persons("Michel","Christine","Mathieu","Julien");
Console.WriteLine("-->Iterator Reverse");
foreach (string s in arrPersons.Reverse)            Console.WriteLine(s);
Console.WriteLine("-->Iterator PositionPaires");
foreach (string s in arrPersons. PositionPaires)    Console.WriteLine(s);
Console.WriteLine("-->Iterator Concat");
foreach (string s in arrPersons.Concat)            Console.WriteLine(s);
Console.ReadLine();
    }

}


程序将产生以下输出:

-->Iterator Reverse
Julien
Mathieu
Christine
Michel
-->Iterator PositionsPaires
Michel
Mathieu
-->Iterator Concat
Julien
Mathieu
Christine
Michel
Michel
Mathieu

yield break 关键字

你如果希望枚举集合中的一些子元素,在这个例子中,yield break关键字正确的告知客户它将中止循环。


public  IEnumerator < string >  GetEnumerator()
{
    
for (int I = 0;I < 2; I++)
        yield 
return m_Names[i];
    yield 
break;
    Console.WriteLine(“Hello”);  
//警告:检测到一个不能到达的代码段
}


 

程序将会输出:
Michel
Christine
 

结果是,书写在yield break指令后的代码不会被编译,编译器如果发现这些不能到达的语句将发出一个警告信息。

yield return yield break 关键字的语法约束
l         yield return yield break 关键字只能用于一个方法,一个属性访问器或者一个操作符(重载方法)的内部;
l         任何使用了 yield return yield break 关键字的方法都必须返回一个以下的接口 System.Collections.Generic.IEnumerable<T>,System.Collections.IEnumerable,System.Collections.Generic.IEnumerator<T> 或者 System.Collections.IEnumerator
l         yield return yield break 关键字不能用在匿名方法的内;
l         yield return yield break 关键字不能用在 finally 块使用;
l         yield return yield break 关键字不能用在 try 块(至少具有一个 catch 块) ;

yield returnyield break关键字不能用在含有refout参数的方法中,可以预示的是包含refout这些关键字的方法并不能返回任何yield关键字约束的内容信息。

一个递归迭代器的例子:
下面这个例子有力的展示了使用C# 2.0迭代器枚举一个non-flat集合,就像一个二叉树:

 

using  System;
using  System.Collections.Generic;

public  class  Node < T >
{
    
public Node ( T item, Node<T> leftNode, Node<T> rightNode)
    
{
        m_Item 
= item;
        m_LeftNode 
= leftNode;
        m_RightNode 
= rightNode;
    }

    
public Node<T> m_LeftNode;
    
public Node<T> m_RightNode;
    
public T m_Item;
}

public  class  BinaryTree < T >
{
    Node
<T> m_Root;
    
public BinaryTree(Node<T> Root)
    
{
        m_Root 
= Root;
    }

    
public IEnumerable<T> InOrder
    
{
        
get
        
{
            
return PrivateScanInOrder(m_Root);
        }

    }

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

        yield 
return root.m_Item;
        
if (root.m_RightNode != null)
            
foreach (T item in PrivateScanInOrder(root.m_RightNode))
            
{
                yield 
return item;
            }

    }

}

class  Program
{
    
static void Main(string[] args)
    
{
        BinaryTree
<string> binaryTree = new BinaryTree<string>(new Node<string>("A",new Node<string>("B",null,null),new Node<string>("C",new Node<string>("D",null,null),new Node<string>(“D”,null,null))));
        
foreach (string s in binaryTree.InOrder)
        
{
            Console.WriteLine(s);
            Console.ReadLine();
        }

    }

}


Main方法中二叉树如下表示:

该程序输出:
B
A
D
C
E

C# 2.0编译器和迭代
如果你是一个好奇的开发者,你可能乐意看到C# 2.0编译解释yield breakyield return关键字所作的工作。这是本节的主题。请注意迭代器是作为语法出现的不像范型,没有IL指令集被添加作为实现迭代。
 
使用编译器自动创建枚举器类
    一个至少包含一个 yield break yield return关键字的方法必须返回一个枚举或枚举器(范型实现或不是)类型。无论如何,你应该考虑的是一个被返回的枚举器对象应该是一个实现了枚举接口并返回一个枚举器类型的实现。如果你读过前面的关于匿名方法的文章,那么你可能已经猜到对于每一个至少包含一个yield breakyield return关键字的方法,编译器为其构建出一种词法环境。如果方法返回了一个枚举对象那么这个创建的类中实现System.Collections.IEnumerable, System.Collections.Generic.IEnumerator<T> 以及 System.Collections接口。此外,这个方法不能直接编译。让我们看一个例子:
using  System;
using  System.Collections.Generic;
class  AClass
{
  
public Ienumerable<string> AnIterateur()
  
{
yield 
return “str1”;
yield 
return “str2”;
yield 
return “str3”;
  }

}

class  Program
{
  
static void Main(string[] args)
  
{
AClass collec 
= new AClass();
foreach ( string s in collec.AnIterateur())
{
  Console.WriteLine(s);
  Console.ReadLine();
}

  }

}


接着我们使用了Reflector查看编译后的配件:


    为了看的更清楚,我们反编译了Main()方法。接着我们会看到一个名为<AnIteratuer>d_0类实例被创建。在前面的论述中我们提过一个匿名方法是某种封闭(Closure)的实例。而C#2.0的迭代器就是一种特殊的封闭类型。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值