C# 2.0 中Iterators的改进与实现原理浅析
C#语言从VB中吸取了一个非常实用的foreach语句。对所有支持IEnumerable接口的类的实例,foreach语句使用统一的接口遍历其子项,使得以前冗长的for循环中繁琐的薄记工作完全由编译器自动完成。支持IEnumerable接口的类通常用一个内嵌类实现IEnumerator接口,并通过IEnumerable.GetEnumerator函数,允许类的使用者如foreach语句完成遍历工作。
这一特性使用起来非常方便,但需要付出一定的代价。Juval Lowy发表在MSDN杂志2004年第5期上的Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes一文中,较为详细地介绍了C# 2.0中迭代支持和其他新特性。 首先,因为IEnumerator.Current属性是一个object类型的值,所以值类型(value type)集合在被foreach语句遍历时,每个值都必须经历一次无用的box和unbox操作;就算是引用类型(reference type)集合,在被foreach语句使用时,也需要有一个冗余的castclass指令,保障枚举出来的值进行类型转换的正确性。 以下为引用: using System.Collections; public class Tokens : IEnumerable { ... Tokens f = new Tokens(...); foreach (string item in f) { Console.WriteLine(item); } ... } 上面的简单代码被自动转换为 以下为引用: Tokens f = new Tokens(...); IEnumerator enum = f.GetEnumerator(); try { do { string item = (string)enum.get_Current(); // 冗余转换 Console.WriteLine(item); } while(enum.MoveNext()); } finally { if(enum is IDisposable) // 需要验证实现IEnumerator接口的类是否支持IDisposable接口 { ((IDisposable)enum).Dispose(); } } 好在C# 2.0中支持了泛型(generic)的概念,提供了强类型的泛型版本IEnumerable定义,伪代码如下: 以下为引用: namespace System.Collections.Generic { public interface IEnumerable<ItemType> { IEnumerator<ItemType> GetEnumerator(); } public interface IEnumerator<ItemType> : IDisposable { ItemType Current{get;} bool MoveNext(); } } 这样一来即保障了遍历集合时的类型安全,又能够对集合的实际类型直接进行操作,避免冗余转换,提高了效率。 以下为引用: using System.Collections.Generic; public class Tokens : IEnumerable<string> { ... // 实现 IEnumerable<string> 接口 Tokens f = new Tokens(...); foreach (string item in f) { Console.WriteLine(item); } } 上面的代码被自动转换为 以下为引用: Tokens f = new Tokens(...); IEnumerator<string> enum = f.GetEnumerator(); try { do { string item = enum.get_Current(); // 无需转换 Console.WriteLine(item); } while(enum.MoveNext()); } finally { if(enum) // 无需验证实现IEnumerator接口的类是否支持IDisposable接口, // 因为所有由编译器自动生成的IEnumerator接口实现类都支持 { ((IDisposable)enum).Dispose(); } } 除了遍历时的冗余转换降低性能外,C#现有版本另一个不爽之处在于实现IEnumerator接口实在太麻烦了。通常都是由一个内嵌类实现IEnumerator接口,而此内嵌类除了get_Current()函数外,其他部分的功能基本上都是相同的,如 以下为引用: public class Tokens : IEnumerable { public string[] elements; Tokens(string source, char[] delimiters) { // Parse the string into tokens: elements = source.Split(delimiters); } public IEnumerator GetEnumerator() { return new TokenEnumerator(this); } // Inner class implements IEnumerator interface: private class TokenEnumerator : IEnumerator { private int position = -1; private Tokens t; public TokenEnumerator(Tokens t) { this.t = t; } // Declare the MoveNext method required by IEnumerator: public bool MoveNext() { if (position < t.elements.Length - 1) { position++; return true; } else { return false; } } // Declare the Reset method required by IEnumerator: public void Reset() { position = -1; } // Declare the Current property required by IEnumerator: public object Current { get // get_Current函数 { return t.elements[position]; } } } ... } 内嵌类TokenEnumerator的position和Tokens实际上是每个实现IEnumerator接口的类共有的,只是Current属性的get函数有所区别而已。这方面C# 2.0做了很大的改进,增加了yield关键字的支持,允许代码逻辑上的重用。上面冗长的代码在C# 2.0中只需要几行,如 以下为引用: using System.Collections.Generic; public class Tokens : IEnumerable<string> { public IEnumerator<string> GetEnumerator() { for(int i = 0; i<elements.Length; i++) yield elements[i]; } ... } GetEnumerator函数是一个C# 2.0支持的迭代块(iterator block),通过yield告诉编译器在什么时候返回什么值,再由编译器自动完成实现IEnumerator<string>接口的薄记工作。而yield break语句支持从迭代块中直接结束,如 以下为引用: 上一篇:一份礼物: 自动填充SqlCommand.Parameters的类(1) 下一篇:Uploading Images to a Database - Part I (转) 更多相关文章
|
推荐文章
精彩文章
|