IEnumerable和Array,IList和List之间有什么区别?

107

IEnumerableArray有什么区别?

IListList有什么区别?

它们似乎具有相同的功能。

9个回答

116

IEnumerable 仅提供了最基本的“可枚举”功能。您可以遍历序列,但仅止于此。
这有劣势;例如,使用 IEnumerable 计算元素数量或获取第 n 个元素非常低效。
但它也有优点;例如,IEnumerable 可以是无限序列,比如质数序列。

Array 是一个具有随机访问的固定大小的集合(即,您可以索引进入它)。

List 是一个具有随机访问和可变大小(即,您可以添加和删除元素)的集合。

IList 是一个接口,将列表功能(计数、添加、删除、索引器访问)与各种具体类(如 ListBindingListObservableCollection 等)分离。


3
根据这里的被接受的答案https://dev59.com/unI-5IYBdhLWcg3wfoa1。IEnumerable上的Count几乎相同,因为它尝试在ICollection上调用Count。 - Karsten
18
是和不是。Count() 扩展方法会检查 IEnumerable 是否也是 ICollection,如果是的话,则调用其 Count 属性。如果 IEnumerable 不是 ICollection,则 Count() 会回退到迭代序列。也许我应该说“仅使用 IEnumerable 计数元素非常低效”,因为当然,IEnumerable 的存在并不妨碍您使用更有效的方法(如果对象有)来计数,LINQ to Objects 扩展方法正是这样做的。 - itowlson

21

IEnumerable是一个接口,允许遍历项的集合(例如使用foreach关键字)。

数组是.NET内置类型。它保存相同类型的项目,但其大小固定。一旦创建具有x元素的数组,它就不能增长或缩小。

IList定义列表的接口,并实现IEnumerable。

List实现了IList接口,它是一个具体的列表类型。

.NET列表和数组之间的区别在于,列表可以添加元素,它们会增长到足够大以容纳所有所需的项目。该列表在内部存储在数组中,当数组不再足够大以容纳所有元素时,将创建一个新数组并将项目复制过去。

IList和数组都实现IEnumerable。这就是接口的工作方式,类实现协定并表现出类似的行为,因此可以被类似地处理(您知道类实现IEnumerable,不需要知道具体细节)。建议您阅读关于接口等内容的相关资料。


1
为了那些不阅读所有答案的读者,添加此评论:数组和List<>都实现了IList<>(数组明确实现了其中大部分,可能是因为许多成员会抛出NotSupportedException异常)。更正确的说法是IList继承自IEnumerable,而不是实现它。 - phoog

10

IEnumerable集合的生成是延迟的。 示例:

public IEnumerable<int> GetTwoInts()
{
  yield return 1;
  yield return 2;
}
public void Something()
{
  var twoInts = GetTwoInts();
}

在方法Something中,对GetTwoInts()的调用实际上不会导致执行GetTwoInts方法,因为该枚举从未被迭代。


请您能否详细说明一下?我不确定我是否理解正确。之后您能否展示一行代码,以便实际执行该方法?谢谢! - paaone
@paaone 将 twoInts 放入 foreach 循环中或调用 twoInts.ToList() - row1

9

IEnumerable和IList是接口。Array和List是类。Array实现了IEnumerable。List实现了继承自IEnumerable的IList。

编辑:如itowlson在评论中提到,Array也实现了IList。


我也不知道!但是如果你尝试在数组上调用IList.RemoveAt会发生什么呢? - Mark Simpson
它会抛出一个NotSupportedException异常。(请注意,IList.RemoveAt和类似的IList方法在Array上被显式实现,以便它们不会显示在普通的Array引用上!) - itowlson
1
@itowlson我想指出,只有System.Array(本身就是一个类)实现了IList,但T[]并没有实现IList<T>。T[]实现的是IEnumerable<T>。我认为OP在问“Array”时并不一定是要System.Array。 - usr-local-ΕΨΗΕΛΩΝ

2

IEnumerable 是一个通用的接口,被许多类(例如 ArrayListString)使用,以便让某人对集合进行迭代。基本上,它是驱动 foreach 语句的东西。

IList 是通常用于向最终用户公开类型为 List 的变量的方式。此接口允许随机访问底层集合。


2
为了补充其他答案,需要注意的是,在执行foreach语句时,IList<T>List<T>之间存在性能差异
这是因为List<T>.GetEnumerator返回的迭代器对象是值类型,而IList<T>.GetEnumerator返回的迭代器对象是引用类型,因此需要进行内存分配(请参见Enumerator of value type of list in c#)。
在我看来,IList<T>不是一个很好的接口。例如,调用Add可能会抛出异常(请参见Why array implements IList?)。如果您需要封装,最好使用IEnumerable<T>IReadOnlyList<T>

2
除了其他答案之外,在使用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",而在第二个示例中,如果我们立即调用 .ToList(),则 .Where() 每次都会被执行,因此看到 "CD" 然后再次看到 "CD",输出两次。因此,每次将 Enumerable 转换为 List 或 Array 时,都会花费一个 O(n) 迭代来遍历集合中的所有项,当集合很大时,这将影响性能。
尽管我们不倾向于以这种方式编写代码,在 LINQ 调用之间调用 .ToList(),但当我们将代码分解为返回 List/Array 而不是 IEnumerable 的可重用方法时,这种情况会更频繁地发生。
然而,这并不意味着我们应该总是操作 IEnumerable。正如 towlson 所提到的那样,例如 .Count() 这样的操作将导致对集合进行迭代,而 List 或 Array 将具有预先计算的信息,因此如果您计划多次调用 .Count(),则转换为 List/Array 将更有效率。这也是为什么 List 上有一个 .Count 属性来计数,而不是一个 .Count() 方法。

0

这是一篇旧帖子,但仍然想回复。 IEnumerable 是一种行为,而 Array 是一种数据结构(连续的元素集合,具有固定大小,便于通过索引访问元素)。 当一个 Array 实现 IEnumerable 时,它应该也展示 IEnumerable 的内在属性(即便于迭代集合)。


0
如果有人最近在阅读这个,IEnumerable 可能是惰性的,所以如果你的迭代器内部有业务逻辑,你必须小心处理:
public IEnumerable<int> GetTwoInts()
{
    Console.WriteLine("1");
    yield return 1;
    Console.WriteLine("2");
    yield return 2;
}

public void Something()
{
    var twoInts = GetTwoInts();
}

使用答案中的示例,只有在“使用”twoInts时,即在foreach循环中使用或调用ToArray时,才会调用两个Console.WriteLine,这可能不直观。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接