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