代码之家  ›  专栏  ›  技术社区  ›  Zaffiro

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

  •  43
  • Zaffiro  · 技术社区  · 16 年前

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

    7 回复  |  直到 7 年前
        1
  •  77
  •   Greg D    16 年前

    IEnumerable<T> 是一个 List<T> . 我怀疑你听到这个的原因 之所以应该使用,是因为它是一个不太严格的接口要求。

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

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

    此方法要求向其传递列表的具体实现。但它只是在有条不紊地做事。它实际上不需要随机访问,也不需要任何其他的东西 列表<T> IList<T> IEnumerable<T> :

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

    现在,我们使用最通用(最不特定)的接口来支持我们需要的操作。这是面向对象设计的一个基本方面。我们减少了耦合,只需要我们需要的东西,而不需要其他很多东西。我们还创造了一个更加 灵活的 foos Queue<T> A. , 任何东西 实现

    所以不是这样 在“性能”或“运行时”方面比列表更有效。就是这样 IEnumerable<T> 是一种更有效的方法吗 设计 构造,因为它是设计要求的更具体指示。(尽管在特定情况下,这可能会带来运行时收益。)

        2
  •  44
  •   McDowell rahul gupta    11 年前

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

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

    首先我来看看延迟执行。突击测验:以下代码将在输入文件中的行上迭代多少次?

    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作为参数,而不是列表。为什么?因为如果需要,您仍然可以将列表传递给函数-列表 数不清的。就这一点而言,数组和许多其他集合类型也是如此。因此,通过在这里使用IEnumerable,您可以使用完全相同的函数并使其更强大,因为它可以处理更多不同类型的数据。

        3
  •  5
  •   Andrew Hare    16 年前

    IEnumerable<T> 它的效率不比 List<T> 作为一个 列表<T> IEnumerable<T>

    这个 IEnumerable<T> 接口只是.NET使用 iterator pattern

    此接口可以在许多类型上实现( 列表<T> 包括)以允许这些类型返回迭代器(即 IEnumerator<T>

        4
  •  3
  •   AnthonyWJones    16 年前

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

     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;
     }
    

    Q :如何获取由GetDigits生成的一组数字并获取总和以将它们相加?
    A. :我需要将GetDigits中的数字集加载到列表对象中,并将其传递给Sum函数。这将使用内存,因为所有数字都需要先加载到内存中,然后才能求和。但是,将Sum的签名更改为:-

     function int Sum(IEnumerable<int> numbers)
    

    这意味着我可以做到:-

     int sumOfDigits = Sum(GetDigits());
    

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

        5
  •  1
  •   Anton Gogolev    16 年前

    var q = from x in ... 这个 q IEnumerable ,但在后台它执行一个非常昂贵的数据库调用。

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

        6
  •  1
  •   Fredrik Mörk    16 年前

    建议让方法返回的一个原因 IEnumerable<T> List<T> IEnumerable<T>

        7
  •  0
  •   jeremyalan    16 年前

    在.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; } } }

    这允许您在不创建新列表的情况下合并这两个列表 列表<int> 对象