解释为什么IEnumerable比List更有效

Zaf*_*iro 47 generics .net-3.5

我一直听说.net 3.5你应该在List上使用IEnumerable,但我找不到任何参考资料或文章来解释为什么它更加精通.有谁知道解释这个的任何内容?

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

Gre*_*g D 70

IEnumerable<T>是一个由.实现的接口List<T>.我怀疑你听到IEnumerable<T>应该使用的原因是因为它是一个不那么紧缩的接口要求.

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

void Output(List<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}
Run Code Online (Sandbox Code Playgroud)

此方法要求传递List的具体实现.但它只是按顺序做某事.它实际上并不需要随机访问或任何其他List<T>甚至是IList<T>给它的东西.相反,该方法应该接受IEnumerable<T>:

void Output(IEnumerable<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}
Run Code Online (Sandbox Code Playgroud)

现在我们使用支持我们所需操作的最通用(最不具体)接口.这是OO设计的一个基本方面.我们通过只需要我们需要的东西来减少耦合,而不仅仅需要其他东西.我们还创建了一个更灵活的方法,因为foos参数可能是a Queue<T>,a List<T>,实现的任何东西IEnumerable<T>.我们不是强迫调用者不必要地将他们的数据结构转换为List.

因此,它IEnumerable<T>不比"性能"或"运行时"方面的列表更有效.这IEnumerable<T>是一种更有效的设计结构,因为它更能说明您的设计需求.(虽然这可以在特定情况下导致运行时增益.)

  • 这都是真的,但是您错过了关于返回“IEnumerable&lt;T&gt;”的方法的延迟执行效率的重要一点。 (3认同)
  • 虽然并非所有返回"IEnumerable <T>"的方法都使用延迟执行(大概这就是为什么原始答案提到"这可能导致特定情况下的运行时增益"). (2认同)
  • 杰夫是对的,这就是我把它放在那里的原因.在这个问题的背景下,我认为任何运行时收益都是锦上添花.IE,是对遵循良好设计原则的奖励.:) (2认同)

Joe*_*orn 42

Enumerables有几个非常好的属性,在将它们转换为列表时会丢失.即他们:

  • 使用延迟/延迟执行
  • 是可组合的
  • 是无限的

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

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);
Run Code Online (Sandbox Code Playgroud)

答案恰好是一个零.在迭代结果之前,它实际上不会做任何工作.您需要在打开文件之前添加此代码:

foreach (string line in lines) Console.WriteLine(line);
Run Code Online (Sandbox Code Playgroud)

即使在代码运行之后,它仍然只能循环一次.将其与您需要迭代此代码中的行的次数进行比较:

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);
Run Code Online (Sandbox Code Playgroud)

即使您忽略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;
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


And*_*are 5

IEnumerable<T>是不效率比一List<T>作为List<T> 一个IEnumerable<T>.

IEnumerable<T>接口是简单地使用.NET程序的方式迭代器模式,仅此而已.

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

  • @devinb:这个问题没有说明性能."熟练"可以指性能或正确性.性能可能是一个问题,但它不是唯一的问题. (3认同)