Generics 解释为什么IEnumerable比列表更有效

Generics 解释为什么IEnumerable比列表更有效,generics,.net-3.5,Generics,.net 3.5,我一直听说在.NET3.5中,您应该在列表中使用IEnumerable,但我找不到任何参考资料或文章来解释为什么它如此精通。有人知道解释这一点的任何内容吗 问这个问题的目的是为了更好地了解IEnumerable在引擎盖下做了什么。如果你能给我提供任何链接,我会做研究并发布答案 IEnumerable并不比列表更有效,因为列表是一个IEnumerable 该接口只是.NET使用的方式,仅此而已 此接口可以在许多类型上实现(List包括在内),以允许这些类型返回迭代器(即的实例),以便调用者可以迭代

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

问这个问题的目的是为了更好地了解IEnumerable在引擎盖下做了什么。如果你能给我提供任何链接,我会做研究并发布答案

IEnumerable
并不比
列表
更有效,因为
列表
是一个
IEnumerable

该接口只是.NET使用的方式,仅此而已

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

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

例如,考虑下面的方法签名:

void Output(List<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}
void输出(列出foos)
{ 
foreach(foos中的var foo){/*做点什么*/}
}
此方法要求向其传递列表的具体实现。但它只是在有条不紊地做事。它实际上不需要随机访问或
列表
甚至
IList
提供的任何其他东西。相反,该方法应该接受一个
IEnumerable

void输出(IEnumerable foos)
{ 
foreach(foos中的var foo){/*做点什么*/}
}
现在,我们使用最通用(最不特定)的接口来支持我们需要的操作。这是面向对象设计的一个基本方面。我们减少了耦合,只需要我们需要的东西,而不需要其他很多东西。我们还创建了一个更灵活的方法,因为
foos
参数可能是
队列
列表
,任何实现
IEnumerable
的东西。我们不会强迫调用方不必要地将其数据结构转换为列表


所以,在“性能”或“运行时”方面,
IEnumerable
并不是比list更有效。这是因为
IEnumerable
是一种更有效的设计构造,因为它更具体地指示了您的设计需要什么。(虽然这在特定情况下会带来运行时收益。)

这是两个不同的野兽,你无法真正比较它们。例如,在
var q=from x in…
中,
q
IEnumerable
,但在后台它执行一个非常昂贵的数据库调用


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

枚举数有几个非常好的属性,在将它们转换为列表时会丢失这些属性。即:

  • 使用延迟/延迟执行
  • 是可组合的
  • 是无限的
首先我来看看延迟执行。突击测验:以下代码将在输入文件中的行上迭代多少次

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);
即使在代码运行之后,它仍然只在行上循环一次。将其与您需要迭代此代码中的行的次数进行比较:

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);
即使忽略
文件.ReadAllLines()
调用并使用来自第一个示例的相同迭代器块,第一个示例仍然会更快。当然,您可以使用列表以同样快的速度编写它,但这样做需要将读取文件的代码与解析文件的代码绑定在一起。因此,您失去了另一个重要特性:可组合性

为了演示可组合性,我将在最后一个特性中添加一个特性—无界系列。考虑FLLLOWIN:

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斐波那契()
{
int n1=1,n2=0,n;
收益率1;
while(true)
{
n=n1+n2;
收益率n;
n2=n1;
n1=n;
}
}
这看起来会永远持续下去,但您可以使用IEnumerable的可组合性属性来构建安全地给出(比如)前50个值或小于给定数字的每个值的东西:

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

最后,IEnumerable更灵活。除非您绝对需要附加到列表或按索引访问项的功能,否则编写函数时最好接受IEnumerable作为参数而不是列表。为什么?因为如果您愿意,您仍然可以将列表传递给函数-列表是IEnumerable。因此因此,通过在这里使用IEnumerable,您可以使用完全相同的函数并使其更强大,因为它可以作用于更多不同类型的数据。

建议方法返回
IEnumerable
的一个原因是它没有
列表
那么具体他的意思是,您可以在以后更改方法的内部结构,以使用更高效的方法来满足它的需要,并且只要它是一个
IEnumerable
,您就不需要触及方法的契约。

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

<>你的代码如果能消耗iQueDybe而不是一个列表就变得更可重用了。
 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;
 }

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

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

公共类MyClass
{
私有
列表
\u listOne; 私有
列表
\u列表二;
公共 foreach (int f in Fibonacci().Take(50)) { /* ... */ } foreach (int f in Fibonacci().TakeWhile(i => i < 1000000) { /* ... */ }
 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;
 }
 function int Sum(IEnumerable<int> numbers)
 int sumOfDigits = Sum(GetDigits());
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; } } }