什么是.NET中的IEnumerable?

74

在.NET中,IEnumerable是什么?有什么解释?


31
说实话,对于初学者来说,在 MSDN 上看到的定义非常可怕,当你只想使用 foreach 时。 - SWeko
13
我能理解为什么它被关闭了,因为在 MSDN 上并不是很清楚,但是我从谷歌上来到这里,这些答案确实帮助了我。如果可以的话,我希望能够投票重新打开...当我在谷歌上搜索“什么是IEnumerable”时,这篇文章排名很高,我相信其他人也会觉得它很有帮助。 - Wil
5个回答

79

这是一个可以循环遍历的东西。它可以是List、Array或者(几乎)任何其他支持foreach循环的东西。当你想要使用一个对象与foreach循环一起使用,但你不知道你正在处理的确切类型时,就可以使用它。可能是数组、列表或者自定义的其它东西。

那么这里的第一个优点是:如果你的方法接受IEnumerable而不是数组或列表,它们会变得更加强大,因为你可以将更多不同类型的对象传递给它们。

现在让IEnumerable真正脱颖而出的是迭代器块(C#中的yield关键字)。迭代器块像List或Array一样实现IEnumerable接口,但它们非常特殊,因为与List或Array不同,它们通常只保存单个项的状态。例如,如果你想循环遍历一个非常大的文件中的行,你可以编写一个迭代器块来处理文件输入。然后你永远不会一次在内存中保存超过一行的文件,并且如果你更早地完成循环(也许它是一个搜索,你找到了需要的东西),你可能不需要读取整个文件。或者如果你正在从大型SQL查询中读取结果,你可以将内存使用限制为一个记录。

另一个特点是,这个评估是“惰性”的,因此如果你在读取时执行复杂的工作来评估可枚举对象,那么该工作直到被要求才会发生。这是非常有利的,因为通常(例如,对于搜索)你会发现你可能根本不需要进行这项工作。

你可以将IEnumerable视为即时List。


2
IEnumarable 的关键在于,支持集合在特定扩展时才被枚举,无论是通过 foreach 还是索引器访问器。不幸的是,IEnumarable 似乎被盲目地用作最低接口,这现在会导致异步数据操作出现问题(例如,在垃圾回收上下文中执行 SQL)。我认为 ICollection 是你所说的除了“惰性评估”之外的所有内容,我们应该使用它,特别是如果数据已经被枚举过一次! - Piotr Kula
1
最好的简单解释,用通俗易懂的英语,致敬! - Jeb50
我认为IEnumerable的命名不太合适。在数学和计算机领域,可枚举对象是指映射到可数集合的任何对象。因此,在.NET之外,IObservable可能是可枚举的。在.NET中,IEnumerable被认为是IObservable的对偶相反,涉及拉取与推送的二元性。IEnumerable的意图似乎是Java所称的Iterable,我认为这是一个更好的名称。 - Sentinel

36

在.NET中,集合类型实现了一个接口,提供迭代器模式。还有一种泛型版本,即IEnumerable<T>

对于实现IEnumerable的集合,用于遍历集合的语法是(你很少看到这种语法,因为有更好看的方法):

IEnumerator enumerator = collection.GetEnumerator();

while(enumerator.MoveNext())
{
    object obj = enumerator.Current;
    // work with the object
}

这与以下函数在功能上等效:

foreach(object obj in collection)
{
    // work with the object
}

如果集合支持索引器,你也可以使用经典的for循环方法来迭代它,但迭代器模式提供了一些额外的好处,比如能够为线程添加同步。


请注意,这里显示的语法是 foreach 的 C# 5 等效语法。在 C# 4 及更早版本中,object obj; 在 while 循环外声明,这会改变循环与捕获变量(如匿名函数)一起使用时的行为。 - Scott Chamberlain
技术堆砌式的回答只会带来更多的困惑和沮丧。 - Jeb50

13

首先它是一个接口。根据MSDN的定义:

公开枚举器,支持对非泛型集合进行简单迭代。

简单来说,任何实现此接口的对象将提供一种获取枚举器的方法。例如可以使用枚举器与foreach一起使用。

List实现了IEnumerable接口。

    // This is a collection that eventually we will use an Enumertor to loop through
    // rather than a typical index number if we used a for loop.
    List<string> dinosaurs = new List<string>();

    dinosaurs.Add("Tyrannosaurus");
    dinosaurs.Add("Amargasaurus");
    dinosaurs.Add("Mamenchisaurus");
    dinosaurs.Add("Deinonychus");
    dinosaurs.Add("Compsognathus");

    Console.WriteLine();

    // HERE is where the Enumerator is gotten from the List<string> object
    foreach(string dinosaur in dinosaurs)
    {
        Console.WriteLine(dinosaur);
    }

    // You could do a 
    for(int i = 0; i < dinosaurs.Count; i++)
    {
        string dinosaur = dinosaurs[i];

        Console.WriteLine(dinosaur);
    }

使用foreach会使代码看起来更简洁。


7
简短的回答是:它可以在foreach中使用的所有内容。

5
你可以使用GetEnumerator方法循环遍历任何东西,它不必支持IEnumerable接口。 - Joel Coehoorn
是的,但大多数情况下 IEnumerable <==> foreach - SWeko
我猜没有人确切知道我们为什么要使用它们,而不是只是告诉定义,比较何时和为什么应该使用它们。 - TAHA SULTAN TEMURI

0
  • 它是一种基本接口,使我们能够循环或迭代集合。

  • 关于 IEnumerable 最重要的注意事项是,在你遍历一个对象或集合时,它只会保持每个项目的状态。

  • 当你遍历大型对象或集合时,它具有良好的性能,因为它不会加载整个对象到内存中以进行迭代。例如,假设您决定逐行阅读大型文件并在其上执行某些操作,则可以编写自己的ReaderEnumrable来高效地读取文件。

  • 当使用 IEnumerable 编写查询时,您正在使用延迟执行的优势,即在访问查询时才运行查询。

  • 每次遍历集合时,都会创建一个不同的集合。例如,在以下代码中,每次访问 listOfFiles 时,ListOfAllFiles 都会再次执行。

    public static void Main()
    {
        var listOfFiles = ListOfAllFiles();
    var filesCount = listOfFiles.Count(); var filesAny = listOfFiles.Any(); var fileIterate = listOfFiles.Select(x => x.FullName); } private static IEnumerable<FileInfo> ListOfAllFiles() { foreach(var file in Directory.EnumerateFiles(Environment.CurrentDirectory)) { yield return new FileInfo(file); } }

你的大部分假设都是关于“yield return”的特性,而不是特定于IEnumerable。IEnumerable只是提供迭代方式的接口,没有其他作用。 - vasil oreshenski
当您使用IEnumerable时,您正在使用yield return,因此它们是完全相关的。 - Seyedraouf Modarresi

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