泛型-解释为什么IEnumerable比Lis更有效

我一直听说,在.net 3.5中,您应该在列表上使用IEnumerable,但是我找不到任何参考资料或文章来解释为什么它如此精通。 有人知道任何解释此内容的内容吗?

提出这个问题的目的是为了更好地了解IEnumerable在幕后所做的事情。 如果您可以提供任何链接,我将进行研究并发布答案。

Zaffiro asked 2020-06-19T01:09:06Z
7个解决方案
70 votes

IEnumerable<T>是由IEnumerable<T>实现的接口。我怀疑您听到应该使用List<T>的原因是,这是一个不太严格的接口要求。

例如,考虑以下方法签名:

void Output(List<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}

此方法要求将其传递给List的具体实现。 但这只是按顺序进行。 它实际上并不需要随机访问或IEnumerable<T>甚至IEnumerable<T>给它的任何其他东西。 而是,该方法应接受List<T>

void Output(IEnumerable<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}

现在,我们正在使用最通用(最不特定)的接口来支持所需的操作。 这是面向对象设计的基本方面。 我们只需要我们需要的东西,而不是其他很多东西,从而减少了耦合。 我们还创建了一个更灵活的方法,因为IEnumerable<T>参数可能是IEnumerable<T>List<T>,而任何实现IEnumerable<T>的参数。我们并没有强迫调用者不必要地将其数据结构转换为List。

因此,在“性能”或“运行时”方面,并不是IEnumerable<T>比列表更有效。 IEnumerable<T>是一种更有效的设计结构,因为它可以更具体地指示您的设计要求。 (尽管在某些情况下这会导致运行时收益。)

Greg D answered 2020-06-19T01:09:33Z
42 votes

可枚举具有几个非常好的属性,将它们转换为列表时会丢失。 即他们:

  • 使用延迟/延迟执行
  • 可组合
  • 无界

首先,我将介绍延迟执行。 弹出测验:以下代码将在输入文件中的行上迭代多少次?

IEnumerable<string> ReadLines(string fileName)
{
    using (var rdr = new StreamReader(fileName) )
    {
       string line;
       while ( (line = rdr.ReadLine()) != null) yield return line;
    }
}


var SearchIDs = new int[] {1234,4321, 9802};

var lines = ReadLines("SomeFile.txt")
              .Where(l => l.Length > 10 && l.StartsWith("ID: "));
              .Select(l => int.Parse(l.Substring(4).Trim()));
              .Intersect(SearchIDs);

答案就是一个零。 在您遍历结果之前,它实际上不会做任何工作。 您需要先添加以下代码,然后才能打开文件:

foreach (string line in lines) Console.WriteLine(line);

即使在代码运行之后,它仍然只会在行上循环一次。 将其与需要遍历这段代码中的行的次数进行比较:

var SearchIDs = new int[] {1234,4321, 9802};
var lines = File.ReadAllLines("SomeFile.txt"); //creates a list
lines = lines.Where(l => l.Length > 10 && l.StartsWith("ID: ")).ToList();
var ids = lines.Select(l => int.Parse(l.Substring(4).Trim())).ToList();
ids = ids.Intersect(SearchIDs).ToList();

foreach (string line in lines) Console.WriteLine(line);

即使您忽略File.ReadAllLines()调用并使用第一个样本中的相同迭代器块,第一个样本仍会更快。 当然,您可以使用列表将其编写为与之一样快,但是要做到这一点,需要将读取文件的代码与解析该文件的代码绑定在一起。 因此,您失去了另一个重要功能:可组合性。

为了演示可组合性,我将添加最后一项功能-无界系列。 考虑以下:

IEnumerable<int> Fibonacci()
{
   int n1 = 1, n2 = 0, n;
   yield return 1;
   while (true)
   {
        n = n1 + n2;
        yield return n;
        n2 = n1;
        n1 = n;
   }
}

看起来这将永远消失,但是您可以使用IEnumerable的可组合性属性来构建可以安全地给出前50个值或每个小于给定数字的值的东西:

  foreach (int f in Fibonacci().Take(50)) { /* ... */ }
  foreach (int f in Fibonacci().TakeWhile(i => i < 1000000) { /* ... */ }

最后,IEnumerable更加灵活。 除非您绝对需要具有附加到列表或按索引访问项目的功能,否则几乎总是会编写更好的函数来接受IEnumerables作为参数而不是Lists。 为什么? 因为您仍然可以根据需要将列表传递给函数-列表是IEnumerable。 就此而言,数组也是这样,许多其他集合类型也很好。 因此,通过在此处使用IEnumerable,您可以使用完全相同的功能并使它更强大,因为它可以处理更多不同类型的数据。

Joel Coehoorn answered 2020-06-19T01:10:39Z
5 votes

IEnumerator<T>的效率不比List<T>高,因为List<T>IEnumerable<T>

IEnumerator<T>接口只是.NET使用迭代器模式的方式,仅此而已。

可以在许多类型(包括IEnumerator<T>)上实现此接口,以允许这些类型返回迭代器(即IEnumerator<T>的实例),以便调用者可以迭代一系列项。

Andrew Hare answered 2020-06-19T01:11:08Z
3 votes

这不是效率问题(尽管可能是对的),而是灵活性。

如果您的代码可以使用IEnumerable而不是List,那么它将变得更加可重用。 AS可以有效地考虑以下代码:

 function IEnumerable<int> GetDigits()
 {

    for(int i = 0; i < 10; i++)
       yield return i
 }

 function int Sum(List<int> numbers)
 {
    int result = 0; 
    foreach(int i in numbers)
      result += i;

    return i;
 }

问:如何获取GetDigits生成的一组数字并求和将它们相加?
答:我需要将GetDigits中的一组数字加载到List对象中,并将其传递给Sum函数。 这会占用内存,因为所有数字都必须先加到内存中才能进行求和。 但是将Sum的签名更改为:-

 function int Sum(IEnumerable<int> numbers)

就是说我可以这样做:

 int sumOfDigits = Sum(GetDigits());

没有列表加载到内存中,我只需要存储当前数字和总和的累加器变量。

AnthonyWJones answered 2020-06-19T01:11:51Z
1 votes

这是两种不同的野兽,您无法真正比较它们。 例如,在IEnumerableListIList,但在后台它执行了非常昂贵的数据库调用。

IEnumerable只是Iterator设计模式的接口,而List/IList是数据容器。

Anton Gogolev answered 2020-06-19T01:12:15Z
1 votes

建议使用方法返回IEnumerable<T>的原因之一是它的含义不如List<T>473。这意味着您以后可以更改方法的内部内容,以使用可能更有效的方法来满足需要。 它是IEnumerable<T>,您无需触摸方法的约定。

Fredrik Mörk answered 2020-06-19T01:12:36Z
0 votes

在.NET 3.5中,使用IEnumerable可以编写延迟执行的方法,例如:

public class MyClass
{
   private List<int> _listOne;
   private List<int> _listTwo;
public IEnumerable<int> GetItems () { foreach (int n in _listOne) { yield return n; } foreach (int n in _listTwo) { yield return n; } } }

这使您可以合并两个列表,而无需创建新的List<int>对象。

jeremyalan answered 2020-06-19T01:13:00Z
translate from https://stackoverflow.com:/questions/1347172/explanation-why-ienumerable-is-more-efficient-than-a-list