Linq我应该返回列表<T>或IEnumerable <T>当我仍然可以做更多的事情

Gra*_*zer 5 c# linq memory-management

我有一些返回List of T的方法,比如说GetAllEvents.在某些情况下,我需要按日期或项目上的其他属性过滤事件列表(或任何我的列表).

我知道LINQ查询可以被"链接"或者有x行可以进一步细化它们,并且在你需要在非linq语句中实际使用它们之前,查询将不会执行(如果我错了,请纠正我)对这个信徒.)

我的问题是,如果我的GetAllXXX方法返回我得到的任何List,那么我在执行LINQ的GetAllXXX代码结束时使用的.ToList()方法是什么?我应该返回IEnumerable吗?如果仅用于在实际运行查询之前需要对"结果"做进一步操作的情况.

这是我担心的一个例子:我说过1000个事件.GetAllEvents将检索所有1000并给我一个列表.然后,根据用户所在的页面,可能仅显示今天,本周或某个类别的事件.理想情况下,当它到达我向用户显示今天发生的5个事件的时候,我真的不想通过线路传递所有1000,然后将其截断到他们真正想要的5.是的,我知道此时它是所有服务器端,但如果仍然为1000分配内存,我试图避免这种情况.

任何指针或建议?

Chr*_*isF 12

回来一个IEnumerable.

转换为a List是快速而轻松的,另外,您可以将界面与方法的实现和输出的使用分离.

关于您的具体担心 - 如果返回所有1000个事件并在客户端上处理它们会很昂贵,那么您应该考虑在服务器上进行一些过滤.您仍然可以使用返回所有事件的方法,但具有返回最频繁查询的专用/优化版本.今天的事件将是一个很好的例子.

  • 我完全同意.但值得考虑的一个小警告:如果你最终没有将IEnumerable转换为数组或列表,并且你对IEnumerable进行了多次操作,你可能会发现自己无意中多次执行查询,这可能会产生严重的性能问题.有时将返回类型设置为IEnumerable并在返回对象之前将其转换为数组并不是一个坏主意. (4认同)

Eri*_*ert 9

如果您将序列转换为服务器上的列表,那么您在服务器上使用时间和内存,然后通过线路传输整个内容,然后在客户端上使用更多时间和内存来过滤列表.

如果您只是返回序列,那么您通过创建不同的问题来"解决"您的问题.现在,当客户端过滤列表时,他们必须对服务器进行一千次小命中,而不是一次昂贵的命中.同样多的信息通过网络传输,并且在所有每次命中的开销上花费更长的时间.

如果你想要做的是在服务器上执行过滤,那么你可以(1)创建一个代表常见过滤器的自定义API(简单),或者(2)代替返回IQueryable,并实现LINQ提供程序.(很难但功能强大.)IQueryable允许您在客户端构建查询,通过线路将查询发送到服务器,在服务器上运行查询,然后仅提供客户端所需的结果.

我的同事马特沃伦写了一系列关于如何实现IQueryable的文章; 我会从那开始.


小智 8

Eric Lippert的答案很好,但请注意,如果您只想提供一些服务器端过滤(甚至是自定义过滤),则不需要实现整个IQueryable.您可以通过仅实现您实际使用的LINQ函数来创建更简单的LINQ兼容API.例如,考虑定义

interface IOneTripEnumerable<T> : IEnumerable<T>
Run Code Online (Sandbox Code Playgroud)

它只公开一个LINQ兼容的Where方法,返回类型为IOneTripEnumerable

IOneTripEnumerable.Where的实现将返回一个也实现IOneTripEnumerable的新对象,并将过滤器存储为数据成员.调用IOneTripEnumerable.GetEnumerator时,您可以打包过滤器并将它们发送到服务器,然后在一次往返中返回过滤后的结果.

(您还可以实现客户端缓存策略:如果您希望后续调用GetEnumerator以与初始调用相同的结果返回枚举器,只需将结果存储在可枚举对象中.)

如果你有更多的时间并且看到需要,你可以通过添加额外的LINQ方法进一步优化,但只需控制Where(允许在服务器上进行过滤)和GetEnumerator(以便在一个循环中获得所有结果 - 旅行)可以以低成本实现相当不错的结果.您不需要实现整个IQueryable.(注意,Count,Any和Take也是往返优化的非常好的候选者,并且实现起来很简单).