yield return返回哪种类型的类

9

我注意到这段代码可以正常工作:

class Program
{
    public static void Main()
    {
        Int32[ ]numbers = {1,2,3,4,5};

        using (var enumerator = Data().GetEnumerator())
        {

        }
    }

    public static IEnumerable<String> Data()
    {
        yield return "Something";
    }
}

我特别关注using块,因为:

Int32[] numbers = { 1, 2, 3, 4, 5, 6 };

using (var enumerator = numbers.GetEnumerator())
{

}

使用编译器出现错误。很显然,yield return返回的类是IDisposable,而普通的数组枚举器不是。因此,我很好奇:yield return究竟创建了什么?


1个回答

11

IEnumerator<T> 实现了 IDisposable,你可以在 Object Browser 或 MSDN 中看到。

非泛型的 IEnumerator 没有实现 IDisposable 接口。

基本的 Array 类实现了 IEnumerable 但没有实现 IEnumerable<T>。(因为 Array 不是泛型)
具体的数组类型确实实现了 IEnumerable<T>,但它们使用显式实现方式实现了 GetEnumerator()(我不确定原因)。
因此,任何数组类型上可见的 GetEnumerator() 都返回 IEnumerator

泛型的 IEnumerable<T> 实现返回一个 System.SZArrayHelper.SZGenericArrayEnumerator<T>

这个类的源代码(在 Array.cs 中)有以下注释,部分解释了这一点(请记住,所有对泛型数组的支持都追溯到 IEnumerable<T> 不是逆变时的某个时期)。

//--------------------------------------------------------------------------------------- 
// ! READ THIS BEFORE YOU WORK ON THIS CLASS. 
//
// The methods on this class must be written VERY carefully to avoid introducing security holes. 
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]". 
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>, 
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is 
// made:
// 
//   ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates 
// it for type <T> and executes it.
// 
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be 
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.) 
//---------------------------------------------------------------------------------------

顺便说一下,我没有看到任何使用 using 块的代码,就像 OP 所示。考虑到它仅仅是对项目进行循环而不是使用操作系统资源(如连接、文件、位图等),这是否必要呢? - shahkalpesh
1
如果枚举器实现了IDisposable接口,foreach会在finally块中调用Dispose()。如果您自己调用GetEnumerator(),则需要您负责处理它的释放。IDisposable不仅适用于非托管资源;它适用于任何需要确定性清理的内容。另外,Directory.EnumerateFiles()呢? - SLaks
数组类型隐式实现IEnumerable和显式实现IEnumerable<T>的原因是向后兼容性。存在一个非泛型的GetEnumerator实例方法,如果没有它,就不能进行更重大的破坏性更改。大多数其他使用的集合都是在.NET 2.0之后添加的,因此在它们被创建时就存在IEnumerable<T>,这可以成为隐式实现的接口。任何在此之前存在的集合很可能不会隐式实现IEnumerable<T>(再说一遍,大多数根本无法实现...) - Servy
@Servy:你确定将GetEnumerator()更改为返回实现其先前返回类型的类型是一种破坏性变化吗?我怀疑这主要是由于差异问题。(数组实现了多个IEnumerable<T> - SLaks
@SLaks:感谢您的回复。我认为您会同意需要使用 Dispose 来清理已使用的操作系统资源(这需要确定性的清理,而不是等待垃圾回收器进行清理)。从这个意义上说,使用 foreach 处理 Directory.EnumerateFiles 要比使用 for(;;)GetNumerator 更好。这样做是否公平? - shahkalpesh
@shahkalpesh:不是的。Dispose()也可能需要用于其他事情(例如,释放读锁或进行引用计数)。而且,foreach()并不是消耗IEnumerator的唯一方法(例如,许多LINQ方法都不使用它)。只要确保处理它(通常使用using),就可以了。 - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接