很难回答这个问题,因为很容易发表意见而不是答案。但我会尽力。
这是一个关于责任的基本问题,更具体地说,是关于良好API设计的问题。
以下是我对此涉及到的API设计领域的一些思考:
我来举几个例子。如果你的方法可以接受任何集合,请使用IEnumerable<T>
。如果你的方法需要一个可索引的集合,请使用IList<T>
或类似类型。如果方法的第一件事就是通过.ToList()
将输入转换为列表,请公开说明。如果我已经在外部有一个列表,并想要把它传递给你的方法,请使用List<T>
。
但是,我必须意识到一旦你声明接受IList<T>
,你就能修改该列表。如果可以接受这点,那么很好,我可以简单地把我的列表发送给你。否则,我将不得不在外部执行.ToList()
。这并不比以前更糟糕,只是一个关于谁负责执行此操作的问题。
相反地,如果你的方法返回一个List<T>
对象,请返回它作为List<T>
或IList<T>
。不要隐藏在IEnumerable<T>
后面。
在这里,您还应该意识到任何收到这个列表的调用者都能修改它。如果不能接受这点,请不要返回IList<T>
。
尽管如此,在最后一个例子中,您可以说如果那个列表不应该在外部被修改(它不是调用者的列表),则应将其隐藏在IEnumerable<T>
后面,但这只是隐藏事实的方式,程序员仍然可以将其强制转换回IList<T>
并进行修改。如果不能接受这一点,请通过.ToList()
返回副本。
然而,所有这些都归结于责任。
如果您的方法返回IEnumerable<T>
,作为使用该方法编写代码的人,我应该依赖于您的方法正确工作。如果您的方法无法工作,因为您已经处置了数据库上下文,那么这是您的问题,您的错误,您的bug。
同样地,如果我需要的东西可以被索引,但除此之外是一个纯只读集合,我应该设计我的方法以采取适当的集合接口来表示这一点。如果我设计我的方法以采取
IEnumerable<T>
,我应该知道我具体地说“我会采取任何惰性生成的集合”。如果我不知道这一点或不想这么说,那么我就不应该采取
IEnumerable<T>
。
因此,为了回答您特定代码行的根本问题,在此必须在内部调用
.ToList()
,否则您的方法是错误的、有缺陷的、错误的。
OR,您可以重新设计您的方法,使其保持它的惰性特性,但在准备好之前不要处理数据库上下文。在这方面对您的方法进行简单的更改将使用
yield return
:
using (var context = ... )
{
foreach (var element in context.Request(...))
yield return element;
}
通过这样的更改,您仍然将去到数据库的实际成本推迟到调用者实际迭代您返回的集合时,并且您仍然正确地处理上下文(假设调用者正确进行迭代)。在这里,我仍会返回 IEnumerable<T>
,以明确表示“我是惰性评估的”。
然而,如果您内部生成的列表未被保存,没有人对其负责,您实际上将这个列表的责任交给了调用者,那么您绝对应该将其作为 List<T>
返回。不要将其隐藏在 IEnumerable<T>
后面。