何时使用List<T>、IEnumerable<T>和ArrayList

3
我的问题很简单。何时应该使用List、IEnumerable和ArrayList?
这是我的情况,我正在使用LINQ开发Web应用程序。信息以IEnumerable的形式返回:
IEnumerable<Inventory> result = from Inventory i in db where.... 

我不确定IEnumerable是如何工作的,但每个操作都需要很长时间才能执行。具体来说,result.Count()、result.ElementAt(i)、result.ToList等每个操作都需要相当长的时间。

因此,我在想是否应该通过result.ToList将其视为List,而不是使用IEnumerable变量。

谢谢!


你正在使用哪个LINQ提供程序(即,数据来自哪里)?列表的大小是多少? - Oded
但是在这种情况下,result.ToList 也可以与 IEnumerable 一起使用。 - L.B
@L.B的意思是,你所看到的慢速并不是因为使用IEnumeralbe<T> - Oded
IEnumerable本身并不起作用,它取决于其实现方式。List<T>IEnumerable,因为它实现了它。 - remio
@Oded 我正在使用db4o作为我的数据库引擎。列表的大小会有所变化,但大约有800个项目。我添加了一个foreach语句并调试了我的IEnumerable列表。第一个对象(索引0)加载需要很长时间,但之后它可以正常工作。如果我使用List元素,result.ToList()需要一些时间,但至少访问元素不会成为问题。 - Gonzalo
您是否使用Linq to SQL获取库存集合? - TheGeekYouNeed
7个回答

6
如果我理解你的操作正确,你有一个查询像 from Inventory i in db select i 然后你会对查询结果进行几个操作:
var count = result.Count();
var fifth = result.ElementAt(5);
var allItems = result.ToList();

现在考虑当您拥有不同类型的查询时会发生什么:
  • IQueryable<T>

    var result = from Inventory i in db select i;
    IQueryable<Inventory> result = from Inventory i in db select i;
    

    The two lines above are the same. They don't actually go to the database, they just create a representation of the query. If you have this, Count() will execute an SQL query like SELECT COUNT(*) FROM Inventory, ElementAt(5) will execute another query that takes only the fifth item in the table and ToList() will execute something like SELECT * FROM Inventory, but that's what we want here.

  • IEnumerable<T>

    IEnumerable<Inventory> result = from Inventory i in db select i;
    

    Doing this again does not go the database, it only creates a representation of the query. But it's a representation that can't use the methods specific to IQueryable<T>, so any LINQ operation will enumerate the collection, which will execute an SQL query like SELECT * FROM Inventory.

    So, for the example: Count() will execute the SELECT * … query only to count the items in the result. ElementAt(5) will execute the whole query again, only to throw away all items except for the fifth. And ToList() will execute the query one more time.

  • List<T>

    List<Inventory> result = (from Inventory i in db select i).ToList();
    

    This will actually execute the SELECT * FROM Inventory query immediately and once. All operations you do with result won't touch the database, they will be done in-memory.

从中应该得出什么结论?首先,永远不要使用IEnumerable<T>作为数据库查询的类型。它的性能非常差。
如果您想对结果进行多个不同的操作,使用IQueryable<T>可能是最佳解决方案。
如果您无论如何都想检索整个结果,请尽快使用ToList()(或ToArray()),然后使用生成的List<T>进行操作。

4
永远不要使用ArrayList。ArrayList是为了与.NET 2.0之前的版本兼容而保留的。它等同于List<object>,在任何普通情况下都没有理由不使用泛型类型。
从您的代码示例中可以看出,您正在使用LINQ to SQL或类似框架从数据库中获取数据。在这种情况下,select语句本身并不获取数据,它只构造查询。当您调用像Count()或ToList()这样的方法时,它才会获取数据-这就是为什么它看起来很慢的原因。它并不比其他方法慢,只是延迟加载正在发挥作用。
使用IEnumerable的好处是您不必一次加载所有数据。如果您只查询特定的where子句或调用Take(1)以获取第一个元素,则LINQ提供程序应该足够智能,只从DB中获取必要的元素。但是,如果您调用Count()或ToList(),它必须检索整个数据集。如果您发现自己需要此类信息,则可能需要调用ToListToArray,并在内存中的列表上执行其余操作,以便您不必再次访问DB。

2

只有当您调用ToList()或其他类似方法时,才会执行您的查询。

这被称为延迟执行

在可能的情况下,对于result,请使用IEnumerable。因为无论最终如何,LINQ的执行性能都不取决于您用什么作为result,它总是被视为IEnumerable。

但是,LINQ的性能取决于底层数据。

[已编辑详细信息]


1
这是错误的。执行数据库LINQ查询的性能可能会严重依赖于您使用IQueryable<T>(有效的SQL查询),IEnumerable<T>(可能是非常低效的SQL查询)或List<T>(通过调用ToList()一次,可能非常低效,但之后很快)。 - svick
@Andriy 我已经调试了我的代码,LINQ 的性能还可以,问题出在访问我的 IEnumerable 变量或任何其他方法时。 - Gonzalo
1
是的,但这取决于查询所涉及的底层数据。我理解问题是关于结果变量的。 - Andriy Buday
@Gonzalo,如果我理解你的问题正确的话,也许我们可以处理延迟执行:http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx - Andriy Buday

1
使用IEnumerable或IList的区别实际上非常简单(表面上)。
您应该查看两个接口定义的契约。IEnumerable仅允许您枚举序列。换句话说,访问数据的唯一方式是使用枚举器,通常在foreach循环中。因此,计数函数的天真实现将类似于:
public static int Count(this IEnumerable<T> source) {
    int count = 0;
    foreach(var item in myEnumerable)
    {
        count++;
    }
    return count;
}

这意味着计算可枚举项数所需的时间将随着项数的增加而线性增加。此外,由于这不以任何方式内部存储,因此每次需要计数时都必须执行此循环。

IList已经公开了Count属性。这是合同的一部分。要在其上实现Count(),只需包装对Count属性的调用。这将花费相同的时间,无论项数如何。

一个简单的思考方式(特别是使用Linq)是将IEnumerable视为您需要的项目的规范。只要您不访问数据,构建它几乎不需要任何时间。一旦开始枚举(返回除IEnumerable之外的任何内容),代码将执行,这可能需要一些时间。

至于您的上下文,我通常喜欢在控制器中保持Linq执行。因此,我进行查询构建,然后在发送到视图之前将其ToList或ToArray。原因非常简单:如果我必须在视图中做更多事情而不仅仅是访问数据,这意味着我在视图中做了太多事情。现在我被迫将该逻辑移动到我的控制器操作中,使我的视图尽可能干净。


0

关于使用哪个,答案是“取决于情况,但大多数情况下使用List”。

根据您问题的完整内容(长时间运行.Count()和其他方法),您应该首先对查询结果执行toList(),然后将其用于任何进一步的访问。

原因在于,IEnumerable基本上是一个查询。由于被查询的数据可以在查询运行之间发生更改,因此在该IEnumerable上进行的每个方法调用都会导致另一个数据库查找。

因此,每次调用.Count()时,都必须去数据库获取与您的查询匹配的所有对象的计数。即使x不变,每次执行elementAt(x),某人仍然需要通过数据库并获取其中的任何内容,因为IEnumerable不能假设数据没有更改。

另一方面,如果您使用List获取了查询的快照,则获取Count或访问随机元素非常快速。

那么,该使用哪个 - 这取决于情况。如果每次访问IEnumerable时,您需要知道数据库(或任何数据源)中的内容是什么,那么您必须使用IEnumerable。如果您只关心在执行初始查询时存在的内容或需要对一致(和/或静态)的数据源执行操作,则使用List。您仍然会在第一次访问时花费时间,但其他所有操作都将很快。


0
如果您对 Linq 查询提供程序使用 Linq 表达式,结果将是一个 IQueryable<T>,它是 IEnumerable<T> 的扩展。
每次迭代 IQueryable<T> 时,Linq 查询提供程序将针对底层数据源执行查询。因此,如果您想多次遍历结果,则将其先转换为列表(.ToList())可能更有效率。
请注意,在将结果转换为列表时,应使用 List<T> 的实际成员而不是 IEnumerable<T> 的扩展方法。例如,list.ElementAt(i)list.Count() 都以 O(n) 时间执行,而 list[i]list.Count 则在常数时间内执行。

0

尽可能使用通用列表/ IEnumerable。

避免使用ArrayList。这可能会导致值类型的装箱和引用类型的强制转换。除非处理对象,否则最好避免使用IEnumerable

IEnumerable<T>展示了非常好的协变、逆变特性。然而它显示了延迟执行,这既是一种祝福也是一种诅咒。

List<T>更适合内部使用,同时将接口公开为IEnumerable<T>List<T>不支持逆变。


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