何时使用List<;T>;,IEnumerable<;T>;和ArrayList

本文关键字:gt lt ArrayList IEnumerable List 何时使 | 更新日期: 2023-09-27 18:28:54

我的问题很简单。我应该在什么时候使用List、IEnumerable和ArrayList。

这是我的场景。我正在一个使用LINQ的Web应用程序中工作。信息以IEnumerable:形式返回

IEnumerable<Inventory> result = from Inventory i in db where.... 

我不确定IEnumerable是如何工作的,但每个操作都需要花费大量时间来执行。更具体地说,结果。Count(),结果。ElementAt(i),结果。ToList等,每个操作都需要相当长的时间。

所以,我想知道我是否应该通过做结果来把它当作一个列表。ToList,而不是使用IEnumerable变量。

谢谢!

何时使用List<;T>;,IEnumerable<;T>;和ArrayList

如果我理解你做得正确,你会得到一个像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;
    

    上面的两行是一样的。它们实际上并没有进入数据库,只是创建了查询的表示形式。如果有,Count()将执行类似SELECT COUNT(*) FROM Inventory的SQL查询,ElementAt(5)将执行另一个只接受表中第五项的查询,而ToList()将执行类似于SELECT * FROM Inventory的查询,但这正是我们想要的。

  • IEnumerable<T>

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

    再次执行此操作不会转到数据库,只会创建查询的表示形式。但它是一种不能使用IQueryable<T>特定方法的表示,因此任何LINQ操作都会枚举集合,该集合将执行类似SELECT * FROM Inventory的SQL查询。

    因此,例如:Count()将执行SELECT * …查询,只对结果中的项进行计数。ElementAt(5)将再次执行整个查询,只会丢弃除第五项之外的所有项。ToList()将再次执行查询

  • List<T>

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

    这实际上会立即执行SELECT * FROM Inventory查询,并执行一次。您使用result所做的所有操作都不会触及数据库,它们将在内存中完成。

你应该从中得到什么?首先,永远不要使用IEnumerable<T>作为数据库查询的类型。它的表现很糟糕。

如果您想对结果进行几个不同的操作,那么使用IQueryable<T>可能是最好的解决方案。

如果仍要检索整个结果,请尽快使用ToList()(或ToArray()),然后使用生成的List<T>

永远不要使用ArrayList。ArrayList是为了与.NET 2.0之前的版本兼容而保留的。它相当于List<object>,在任何正常情况下都没有理由不使用泛型类型。

从您的代码示例中可以看出,您正在使用LINQ to SQL或类似的框架从数据库中获取数据。在这种情况下,select语句本身并不带来数据,它只是构造查询。当您调用像Count()或ToList()这样的方法时,它会获取数据——这就是它看起来很慢的原因。它并没有变慢,只是动作中的懒惰加载。

使用IEnumerable的优点是不必一次加载所有数据。如果您只是使用特定的where子句进行查询,或者调用Take(1)来获取第一个元素,那么LINQ提供程序应该足够聪明,只从DB中获取必要的元素。但是,如果您调用Count()或ToList(),它必须检索整个数据集。如果你发现自己需要这类信息,你可能会想调用ToListToArray,并在内存列表上完成其余的工作,这样你就不必再次访问数据库了。

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

这就是所谓的拒绝执行。

只要您的result有可能,请使用IEnumerable。LINQ的执行性能并不取决于result使用什么,因为最终它被视为IEnumerable。

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

[已与详细信息一起编辑]

使用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。原因很简单:如果我必须做的不仅仅是访问视图中的数据,那就意味着我在视图中做得太多了。我现在被迫将这种逻辑转移到我的控制器操作中,尽可能保持我的观点清晰。

如果对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在恒定时间内执行。

尽可能使用GenericLists/IEnumerable。

避免ArrayList。这可能导致对值类型进行装箱,并对引用类型进行强制转换。IEnumerable也是一样——除非你处理对象,否则最好避免。

CCD_ 43具有很好的协方差、反方差特征。然而,它显示了delayed execution,这既是一种祝福,也是一种诅咒。

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

使用的答案是"这取决于,但主要使用List"。

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

原因如下。IEnumerable基本上是一个查询。由于被查询的数据可能在查询运行之间发生变化,因此对该IEnumerable的任何一个方法调用都会导致另一个数据库查找。

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

另一方面,如果您已经使用List获得了查询的快照,那么获取Count或访问随机元素是非常快的。

所以,使用哪一种——这取决于情况。如果每次访问IEnumerable时,您都需要立即知道数据库(或任何数据源)中的内容,那么您就必须使用IEnumerable。如果您只关心执行初始查询时有什么,或者需要对一致(和/或静态)数据源执行操作,请使用List。你的第一次访问仍然会受到时间的影响,但其他一切都会很快。