【发布时间】:2015-08-30 16:33:16
【问题描述】:
我原以为为实现IEnumerable<T> 的空集合执行以下代码会引发异常:
var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // Surely should throw?
因为集合是空的,所以访问IEnumerator.Current 是无效的,我本来预计会出现异常。但是,List<T> 不会抛出异常。
the documentation for IEnumerator<T>.Current 允许这样做,它指出 Current 在以下任何条件下都是未定义的:
- 枚举器位于集合中第一个元素之前,紧接在创建枚举器之后。在读取 Current 的值之前,必须调用 MoveNext 以将枚举数前进到集合的第一个元素。
- 最后一次调用 MoveNext 返回 false,表示结束 集合。
- 枚举器因集合中的更改而失效,例如添加、修改或删除元素。
(我假设“未能抛出异常”可以归类为“未定义的行为”......)
但是,如果您执行相同的操作,但使用 IEnumerable 代替,您会遇到异常。此行为由 the documentation for IEnumerator.Current 指定,其中指出:
- 如果最后一次调用 MoveNext 返回 false,Current 应该抛出 InvalidOperationException,这表示集合结束。
我的问题是:为什么会有这种差异?有什么我不知道的技术原因吗?
这意味着看起来相同的代码的行为会因使用IEnumerable<T> 或IEnumerable 而有很大不同,如下程序所示(注意showElementType1() 和showElementType1() 中的代码是如何相同的):
using System;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApplication2
{
class Program
{
public static void Main()
{
var list = new List<int>();
showElementType1(list); // Does not throw an exception.
showElementType2(list); // Throws an exception.
}
private static void showElementType1(IEnumerable<int> collection)
{
var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // No exception thrown here.
Console.WriteLine(type);
}
private static void showElementType2(IEnumerable collection)
{
var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // InvalidOperationException thrown here.
Console.WriteLine(type);
}
}
}
【问题讨论】:
-
但在第一种情况下 - T - int 不能为 null,所以也不例外,但在第二种情况下 - Curretn 是对象,所以默认为 null
-
我一直在写这个作为答案,但感觉还不够好,所以... 性能 - 通用集合的设计旨在解决装箱值类型的巨大性能问题一份清单(当然还有其他原因)。在每个
Current访问上检查hasValue听起来并不多,但考虑到 LINQ 也在那个时候出现 - 使用枚举器大量,即使对于曾经是简单数组迭代的事情也是如此.无论如何,您都应该检查MoveNext,因此当MoveNext返回false时,允许Current任意设置并没有太大伤害。 -
List<T>的源代码:current = default(T); -
@Grundy 不管它是否是引用类型都会发生,所以这与它无关。您可以将我的示例代码更改为
List<string>,同样的事情也会发生。 -
值类型的长期泄漏抽象的另一个副作用。并不是说框架无法检查这一点,这样做的代码太昂贵了,无法添加到 Current 的每次使用中。
标签: c# ienumerable