【问题标题】:In which cases do I need to create two different extension methods for IEnumerable and IQueryable?在哪些情况下需要为 IEnumerable 和 IQueryable 创建两种不同的扩展方法?
【发布时间】:2015-09-03 17:31:11
【问题描述】:

假设我需要一个扩展方法,它只选择来自不同来源的必需属性。源可以是数据库或内存中的集合。所以我定义了这样的扩展方法:

 public IQueryable<TResult> SelectDynamic<TResult>(
            this IQueryable<T> source,
            ...)

这适用于IQueryables。但是,我也必须为IEnumerables 调用此函数。

在这种情况下,我可以在.AsQueryable() 的帮助下调用它:

  myEnumerable.AsQueryable()
        .SelectDynamic(...)
        .ToList();

两者都工作正常。如果两者都能正常工作,在哪些条件下我必须为同一目的创建两种不同的扩展方法,一种用于IEnumerable,另一种用于IQueryable

如果是Queryable,我的方法必须向数据库发送查询。

例如这里是System.Linq命名空间内.Select扩展方法的来源:

我再次重复我的主要问题:

Queryable 的情况下,我的方法必须向数据库发送查询,但在使用IEnumerable 时则不需要。现在,我使用AsQueryable() 作为可枚举项。因为,我不想为Enumerable 编写相同的代码。 会不会有一些副作用?

【问题讨论】:

  • 其实,我认为这是一个很好的问题,因为似乎没有人知道 AsQueryable 的真正作用。

标签: c# .net linq ienumerable iqueryable


【解决方案1】:

如果您的代码仅在其处理的对象加载到内存中时才真正起作用,只需提供 IEnumerable 变体并让您的调用者决定何时要将 IQueryable 转换为内存中IEnumerable.

通常,除非您正在编写新的数据库提供程序,否则您不会围绕 IQueryable 实现新的变体。

【讨论】:

  • 我有一半(糟糕)的书面答案是相同的 - 如果您正在编写 IQueryable 扩展,您需要确保您始终将其视为 IQueryable 而不是实际上使查询在您的代码中得到解析
  • 我的方法必须在Queryable 的情况下向数据库发送查询,但在使用IEnumerable 时则不需要。现在,我使用AsQueryable 表示可数。因为,我不想为可枚举项编写相同的代码。会不会有副作用?
【解决方案2】:

myEnumerable.AsQueryable() 返回一个自定义对象:new EnumerableQuery&lt;TElement&gt;(myEnumerable); (source code)

这个EnumerableQuery 类实现了IEnumerable&lt;T&gt;IQueryable&lt;T&gt;

当使用.AsQueryable()EnumerableQuery 结果作为IEnumerable 时,接口方法IEnumerable&lt;T&gt;.GetIterator() 的实现只返回原始源迭代器,因此无需更改且开销最小。

当使用.AsQueryable() 的结果作为IQueriable 时,接口属性IQueriable.Expression 的实现只返回Expression.Constant(this),准备好稍后在消耗整个表达式树时作为IEnumerable 进行评估。

(据我所知,当 EnumerableQuery 直接从 IEnumerable 构造时,EnumerableQuery 的所有其他方法和代码路径并不真正相关)

如果我理解正确,您已经实现了您的方法 selectDynamic&lt;TResult&gt;(),您在方法内部构造了一个表达式树,编译时会产生所需的结果。

据我了解源代码,当您调用例如myEnumerable.AsEnumerable().selectDynamic().ToList(),您构建的表达式树是在 myEnumerable 上编译和执行的,总开销应该相当小,因为所有这些工作只在每个查询中完成一次,而不是每个元素一次。

所以我认为像这样实现您的 IEnumerable Extension 方法没有任何问题:

public IEnumerable<TResult> SelectDynamic<TResult>(
        this IEnumerable<T> source,...)
    return source.AsQueryable().SelectDynamic();
}

有一些轻微的开销,因为每次调用此方法时都会编译一次查询,而且我不确定 JITer 是否足够聪明以缓存此编译。但我认为这在大多数情况下不会引起注意,除非您每秒执行此查询一千次。

以这种方式实现 IEnumerable 扩展方法应该没有其他副作用,除了轻微的性能问题。

【讨论】:

  • 感谢您的回答。我将此标记为答案。这句话让我更好地理解了这个过程:有一些轻微的开销,因为每次调用这个方法时都会编译一次查询,我不确定 JITer 是否足够聪明来缓存这个编译。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-23
  • 2016-02-14
  • 2011-07-28
  • 2011-11-06
  • 2019-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多