Answers:
IEnumerable仅提供最少的“可迭代”功能。您可以遍历序列,仅此而已。这有缺点-例如,使用IEnumerable计数元素或获取第n个元素效率很低-但它也有优点-例如,IEnumerable可以是无穷序列,例如质数序列。
数组是一个固定大小的集合,具有随机访问权限(即,您可以对其进行索引)。
List是一个具有随机访问权限的可变大小的集合(即,您可以添加和删除元素)。
IList是一个接口,它从各种具体类(例如List,BindingList,ObservableCollection等)中抽象出列表功能(计数,添加,删除,索引器访问)。
IEnumerable是一个接口,它允许通过一组项进行迭代(例如,通过foreach关键字)。
数组是.NET固有的。它容纳相同类型的项目,但是大小固定。创建具有x个元素的数组后,它就无法增长或收缩。
IList定义列表的接口,并实现IEnumerable。
List实现IList接口;这是列表的具体类型。
.NET列表和数组之间的区别在于,列表可以添加元素-它们增长到足以容纳所有必需项的大小。列表将其内部存储在一个数组中,当该数组的大小不再足以容纳所有元素时,将创建一个新的数组并复制项目。
IList和数组都实现IEnumerable。接口就是这样工作的-类实现契约并以相似的方式运行,因此可以类似地被对待(您知道该类实现IEnumerable,您不需要知道操作方式或原因)。我建议您阅读接口等内容。
IEnumerable和IList是 接口。数组和列表是类。数组实现IEnumerable。List实现了扩展IEnumerable的IList。
编辑:正如itowlson在评论中提到的,Array还实现了IList。
IEnumerable集合的生成很懒。例:
public IEnumerable<int> GetTwoInts()
{
yield return 1;
yield return 2;
}
public void Something()
{
var twoInts = GetTwoInts();
}
在方法Something中,对GetTwoInts()的调用实际上不会导致方法GetTwoInts的执行,因为永远不会重复枚举。
twoInts输入a foreach或call twoInts.ToList()。
IEnumerable是由许多类,如使用的通用接口Array,List并且String为了让别人迭代一个集合。基本上,这就是驱动foreach语句的原因。
IList通常是如何List向最终用户公开类型的变量。该接口允许随机访问基础集合。
为了补充其他答案,请注意,在执行foreach语句之间和执行foreach语句时,存在性能差异。IList<T>List<T>
这是因为by List<T>.GetEnumerator所返回的迭代器对象是一个值类型,而by IList<T>.GetEnumerator所返回的对象是引用类型,因此需要进行内存分配(请参阅c#中list的枚举类型的枚举器)。
我认为,IList<T>这不是一个很好的界面。例如,调用Add可能抛出(请参阅为什么数组实现IList?)。如果需要封装,最好使用IEnumerable<T>或IReadOnlyList<T>。
除了其他答案之外,了解使用LINQ时Enumerable与List / Array之间的区别可能会对性能产生巨大影响。简而言之,可以将Enumerable视为查询生成器,而List / Array是查询的结果。
在使用EntityFramework的LINQ to SQL的上下文中,前者只是构建SQL查询而不对数据库执行查询,也不将任何数据加载到内存中,而后者则相反。这就是为什么我们会推迟打电话.ToList()到内存中需要它来执行业务逻辑之前。
在其他情况下,返回IEnumerable的LINQ表达式将推迟执行直到.ToList()被调用。考虑下面的示例:
void Main()
{
var w1 = "AB".AsEnumerable();
Console.WriteLine($"W1: 1");
w1 = w1.Where(W1);
Console.WriteLine($"W1: 2");
w1 = w1.Where(W2);
Console.WriteLine($"W1: 3");
w1.ToList();
Console.WriteLine($"----------");
var w2 = "CD".AsEnumerable();
Console.WriteLine($"W2: 1");
w2 = w2.Where(W1);
Console.WriteLine($"W2: 2");
w2 = w2.ToList();
Console.WriteLine($"W2: 3");
w2 = w2.Where(W2);
Console.WriteLine($"W2: 4");
w2.ToList();
Console.WriteLine($"----------");
}
bool W1(char arg)
{
Console.WriteLine($"W1:{arg}");
return true;
}
bool W2(char arg)
{
Console.WriteLine($"W2:{arg}");
return true;
}
OUTPUT:
W1: 1
W1: 2
W1: 3
W1:A
W2:A
W1:B
W2:B
----------
W2: 1
W2: 2
W1:C
W1:D
W2: 3
W2: 4
W2:C
W2:D
----------
在第一个示例中,两个.Where()被“追加”并一起执行,最后一次.ToList()被调用时,首先通过管道先通过项目“ A ”,然后通过项目“ B ”,因此在输出中看到“ AABB ”,而在第二个示例中,在.Where()如果我们调用每次执行.ToList()随即,所以看到“ CD ”,然后选择“ CD ”了,输出的两倍。因此,每次将Enumerable转换为List或Array都会花费一个O(n)集合中的所有项目进行迭代,这将在集合很大时影响性能。
尽管我们不倾向于以这种方式.ToList()在LINQ调用之间调用来编写代码,但是当我们将代码放入返回的可重用方法中List/Array而不是返回时,这种情况会更频繁地发生。IEnumerable。
但是,这并不意味着我们应该一直进行操作IEnumerable。正如towlson所提到的那样,诸如这样的操作.Count()将导致整个集合的迭代,而List或Array将预先计算此信息,因此,List/Array如果您打算.Count()多次调用,则转换为会更有效。这也是为什么.Count列表上有一个属性而不是一种对其.Count()进行计数的方法的原因。