为什么应该在IEnumerable <T>上使用List <T>?


24

在我的ASP.net MVC4 Web应用程序中,我使用IEnumerables,试图遵循这一口头禅来编程接口,而不是实现。

Return IEnumerable(Of Student)

Return New List(Of Student)

人们告诉我使用List而不是IEnumerable,因为列表会强制执行查询,而IEumerable不会。

这真的是最佳做法吗?有没有其他选择?我对使用可以使用界面的具体对象感到奇怪。我的奇怪感觉合理吗?


2
嗯,首先,为什么强制执行查询是一件好事?其次,您应该监视和分析数据库调用,以评估此技术考虑是否具有任何优点。
user16764 2013年

2
据说应该执行所有查询,以便完成模型并为视图加载模型。即视图应该接收所有内容,而不是查询数据库。
Rowan Freeman

3
这个StackOverflow问题很好地覆盖了它。
Karl Bielefeldt

这是一个很好的答案,但我想知道它与MVC的相关性。为什么不能为视图提供IEnumerables,以便所有查询都能及时运行?
Rowan Freeman

2
“据说应该执行所有查询,以便完成模型并为视图准备好加载。也就是说,视图应该接收所有内容,而不是查询数据库。” 废话 如果传递IEnumerable,则您的视图将不知道或不在乎它是否在查询数据库。这就是应该的方式。
user16764 2013年

Answers:


21

有时候,ToList()对linq进行查询对于确保查询按时间和顺序执行可能很重要。但是,这些情况很少见,在真正遇到它们之前,没有人应该担心太多。

长话短说,IEnumerable只要在需要迭代的地方就可以使用,IList在需要直接建立索引并且需要动态大小的数组时使用(如果需要在固定大小的数组上建立索引,则只需使用标准数组即可)。

至于执行时间,您始终可以将列表用作IEnumerable变量,因此可以IEnumerable通过执行来随意返回an .ToList();,也可以通过在on处IEnumerable执行参数来传入参数as,然后立即强制执行。请注意,每次强制执行时,都不要停留在刚刚执行该操作的变量上并再次执行它,否则最终将不必要地使LINQ查询中的迭代次数加倍。.ToList()IEnumerable.ToList()IEnumerable

关于MVC,这里确实没有什么特别需要注意的。它将遵循与.NET其余部分相同的执行时间规则,我想您可能曾经因过去的延迟执行语义而被混乱所困扰,并将其归咎于MVC告诉您,这在某种程度上是相关的,但是不。延迟执行的语义起初会使每个人感到困惑(甚至在之后的一会儿;它们可能有点棘手)。再说一次,只是不用担心,直到您真正关心确保LINQ查询不会被执行两次或要求它相对于其他代码按一定顺序执行时,才将变量分配给它自己.ToList()强制执行,您会没事的。


给出IEnumerables视图是否不好?您是否应该给它提供列表,以便在视图获取它们之前已执行查询?
Rowan Freeman

@RowanFreeman我刚刚添加了一个编辑来回答这个问题。我认为您遇到了一个他们完全不了解的事情(不能责怪他们,延迟执行的过程非常复杂且令人困惑),并将其归咎于糟糕的joojoo,而不是致力于理解延迟执行的全部行为语义。
吉米·霍法

好答案。所以我真的需要使用.ToList()吗?到目前为止,我的应用程序仅使用IEnumerables并将它们从模型传递到视图即可正常工作。没有列表或.ToList()。我的问题不是功能之一-我知道我的应用程序可以工作。我的问题是最佳实践之一。
Rowan Freeman 2013年

4
@RowanFreeman的最佳实践是使用仍然满足您要求的最小接口。IEnumerable现在正在为您执行此操作,因此不必担心更改它。就是说,有一天您将有一个查询执行3或10次并且不了解为什么,或者期望查询在插入之前被执行才发现它随后执行,这是您需要识别.ToList的时间。 ()将在需要时强制执行,并且多次从LINQ查询中重复IEnumerable将多次执行整个查询;当这些事件发生时,请修正执行情况
Jimmy Hoffa 2013年

1
您甚至不应使用List-绕过一个List隐含含义,即列表的内容将被修改。如果要返回集合,请使用IReadOnlyCollectionList用于方法内部,以及在修改列表的方法之间进行交换。而已!
ErikE

7

有两个问题。

IENumerable<Data> query = MyQuery();

//Later
foreach (Data item in query) {
  //Process data
}

在到达“过程数据”循环时,查询可能不再有效。例如,如果查询是在已被Dispose的DataContext上运行的,则您的代码将引发异常。当您在与创建它的位置不同的上下文中处理查询时,这种事情变得非常混乱。

第二个问题是,在“过程数据”循环完成之前,不会释放您的连接。如果“过程数据”很复杂,这只是一个问题。在http://msdn.microsoft.com/zh-cn/library/bb386929.aspx中提到了这一点:

问:我的数据库连接保持打开状态有多长时间?

答:在您使用查询结果之前,连接通常保持打开状态。如果您希望花些时间来处理所有结果并且不反对缓存结果,则将ToList应用于查询。在每个对象仅处理一次的常见方案中,流模型在DataReader和LINQ to SQL中都比较出色。

因此,这些问题就是为什么鼓励您确保例如通过调用来实际执行查询ToList()。但是,正如Jimmy建议的那样,没有什么阻止您将列表作为IEnumerable返回。

通常,我建议避免对IEnumerable进行多次迭代。假设您的代码使用者遵循此规则,那么我认为不必担心有人通过两次执行查询来两次访问数据库。


1

IEnumerable尽早枚举的另一个好处是在适当的位置会抛出异常。这有助于调试。

例如,如果您在一个Razor视图中有一个死锁异常,那么它就不会像在您的一种数据访问方法期间发生异常那样清楚。


任何返回IEnumerable可以抛出的方法都可能会出错。延迟IEnumerable方法应分为两种:一种非延迟方法检查参数并进行设置,并在必要时抛出(例如,由于参数为空)。然后,它返回到其私有实现的呼叫推迟。我认为我的评论与您的​​答案完全不符,但确实认为您在答案中遗漏了一个重要方面,即使用IEnumerablevs. List(变异)vs. IReadOnlyCollection(推迟)
ErikE
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.