Do you ToList()?

25

你是否有默认类型在处理LINQ查询结果时使用?

默认情况下,LINQ将返回一个IEnumerable<> 或者一个IOrderedEnumerable<>。我们发现List<>更加有用,因此我们通常会习惯性地对查询结果进行ToList()操作,并且在函数的参数和返回值中使用List<>

唯一例外的情况是在LINQ to SQL中,调用.ToList()会过早地枚举IEnumerable

我们还广泛使用WCF,默认的集合类型是System.Array。为了与我们代码库中的其他部分保持一致,我们总是将其更改为System.Collections.Generic.List,在VS2008的“服务引用设置”对话框中进行更改。

你的做法是什么?

5个回答

23

ToList 总是立即评估序列 - 不仅在LINQ to SQL中如此。如果您想要这样做,那很好 - 但并不总是适当的。

个人认为我会尽量避免直接声明返回 List<T> - 通常更适合使用 IList<T>,这样可以稍后更改为其他实现。当然,也有一些操作仅在 List<T> 上指定... 这种决策总是棘手的。

编辑:(我本来会把这篇文章发表在评论区,但是会太庞大)延迟执行允许您处理无法放入内存中的数据源。例如,如果您正在处理日志文件 - 将其从一种格式转换为另一种格式,将其上传到数据库中,计算一些统计数据之类的操作 - 您可能非常能够通过流式传输处理任意数量的数据,但是真的不希望将所有内容都吸入内存中。对于您特定的应用程序可能不是问题,但这是需要记住的事情。


1
同意ToList会立即求值的观点。我们认为在LINQtoSQL中,这很可能会产生性能影响(特别是如果我们链接了几个LINQ表达式),但当我们在内存中时,任何性能损失都将是可以忽略的 - 对人类来说一致性更重要。 - Richard Ev
3
除了性能之外,存在显著的差异。 特别是,如果查询(例如源中的数据)发生任何变化,则延迟执行将给出不同的答案。 有时这是你想要的,有时则不是。 - Jon Skeet

16

我们有相同的场景 - WCF通信到服务器,服务器使用LINQtoSQL。

当从服务器请求对象时,我们使用.ToArray(),因为客户端无法更改列表是“非法的”。(也就是说,没有支持“.Add”,“.Remove”等操作的必要)。

然而,在仍在服务器上的情况下,我建议您将其保留为默认值(不是IEnumerable,而是IQueryable)。这样,如果您想根据某些条件进一步过滤,过滤会仍然在SQL端进行直到评估。

这是一个非常重要的观点,因为它意味着根据您的操作,可能会带来难以置信的性能提高或损失。

例子:

// This is just an example... imagine this is on the server only. It's the
// basic method that gets the list of clients.
private IEnumerable<Client> GetClients()
{
    var result = MyDataContext.Clients;  

    return result.AsEnumerable();
}

// This method here is actually called by the user...
public Client[] GetClientsForLoggedInUser()
{
    var clients = GetClients().Where(client=> client.Owner == currentUser);

    return clients.ToArray();
}

你能看到正在发生的事情吗?"GetClients"方法将会强制从数据库中下载所有的“clients”...然后Where子句将在GetClientsForLoggedInUser方法中对其进行过滤。

现在,请注意一下细微的变化:

private IQueryable<Client> GetClients()
{
    var result = MyDataContext.Clients;  

    return result.AsQueryable();
}

现在,实际评估要等到调用“.ToArray”时才会发生...而SQL将进行过滤。好多了!


你的观点非常独特。大多数人似乎都忽略了它。我经常看到类似这样糟糕的例子被使用。人们没有停下来思考在执行时那个转换会产生什么影响。 - Jason Short

7
在Linq-to-Objects中,从函数返回List<T>并不像THE VENERABLE SKEET所指出的那样好,而返回IList<T>则更好。但通常情况下,你仍然可以做得比这更好。如果你返回的东西应该是不可变的,那么IList就是一个糟糕的选择,因为它会引导调用者添加或删除内容。
例如,有时你有一个方法或属性,它返回一个Linq查询的结果或使用yield return来懒惰地生成一个列表,然后你意识到最好在第一次调用时这样做,将结果缓存到List<T>中,并在此之后返回缓存版本。这时返回IList可能是个坏主意,因为调用者可能会修改列表以适应自己的目的,这将破坏你的缓存,使他们的更改对所有其他调用者可见。
最好返回IEnumerable<T>,这样他们只能进行前向迭代。如果调用者想要快速随机访问,即希望能够使用[]按索引访问,他们可以使用ElementAt,Linq定义了它,这样它会静默地嗅探IList并在可用时使用它,如果没有,它就会进行愚蠢的线性查找。
我使用ToList的一个场景是当我有一个混合了自定义运算符和使用yield return来过滤或转换列表的复杂的Linq表达式系统。在调试器中逐步执行可以变得非常混乱,因为它会跳来跳去地进行懒惰评估,所以有时我会暂时在一些地方添加ToList(),这样我就可以更轻松地跟踪执行路径。(尽管如果你执行的事情具有副作用,这可能会改变程序的含义。)

还要注意的是,在.NET 4.5中有一种趋势,即使用新的IReadOnlyList来提供我们喜爱的流行接口(例如IList)的只读版本。http://msdn.microsoft.com/en-us/library/hh192385.aspx - jpierson

2

这取决于您是否需要修改集合。当我知道没有人会添加/删除项目时,我喜欢使用数组。当我需要排序/添加/删除项目时,我使用列表。但通常只要可以,我就将其保留为IEnumerable。


2
如果您不需要List<>的附加功能,为什么不仅使用IQueryable<>?最低公共分母是最好的解决方案(特别是当您看到Timothy的答案时)。

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