在哪些情况下需要为IEnumerable和IQueryable创建两个不同的扩展方法?

13

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

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

这对于 IQueryable 没问题。但是,我还需要为 IEnumerable 调用此函数。

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

  myEnumerable.AsQueryable()
        .SelectDynamic(...)
        .ToList();
两种方法都能正常工作。如果两种方法都能正常工作,在什么情况下我需要为相同的目的创建两个不同的扩展方法,一个用于IEnumerable,另一个用于IQueryable
我的方法必须在使用Queryable时向数据库发送查询。
例如,这是System.Linq命名空间中.Select扩展方法的源代码:

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

我的方法必须在使用Queryable时向数据库发送查询,但在使用IEnumerable时则不需要。现在,我正在对枚举使用AsQueryable()。因为我不想为Enumerable编写相同的代码。这样会有什么副作用吗?


1
其实,我认为这是一个好问题,因为似乎没有人知道 AsQueryable 到底是做什么的。 - usr
2个回答

5
如果你的代码只有在处理的对象已经被加载到内存中时才能正常工作,那么只需提供 IEnumerable 变量,并让你的调用者决定何时将 IQueryable 转换为内存中的 IEnumerable
通常情况下,除非你正在编写新的数据库提供程序,否则不需要实现新的 IQueryable 变体。

我有一个半(糟糕)写的类似答案 - 如果你正在编写IQueryable扩展,你需要尽可能地确保你在整个过程中将其视为IQueryable而不是在代码中实际引起查询被解析。 - James Thorpe
1
我的方法必须在处理 Queryable 时向数据库发送查询,但在处理 IEnumerable 时则不需要。目前,我正在使用 AsQueryable 来处理可枚举对象,因为我不想为可枚举对象编写相同的代码。这样做会有什么副作用吗? - Farhad Jabiyev

2

myEnumerable.AsQueryable()返回一个自定义对象:new EnumerableQuery<TElement>(myEnumerable) (源代码)。

这个EnumerableQuery类实现了IEnumerable<T>IQueryable<T>

当使用EnumerableQuery.AsQueryable()结果作为IEnumerable时,接口方法IEnumerable<T>.GetIterator()的实现只是返回原始源迭代器,因此没有变化和最小的开销。

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

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

如果我理解正确,您已经实现了您的方法selectDynamic<TResult>(),使其在方法内部构造一个表达式树,在编译时产生期望的结果。

据我所知,当你调用例如myEnumerable.AsEnumerable().selectDynamic().ToList()时,你构造的表达式树被编译并在myEnumerable上执行,总体开销应该是非常小的,因为所有这些工作只在每个查询中完成一次,而不是在每个元素中重复一次。

所以我认为用这种方式实现您的IEnumerable扩展方法是没有问题的:

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

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

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


感谢您的回答。我将其标记为答案。这个句子让我更好地理解了这个过程:由于每次调用该方法都会编译查询,所以存在一些轻微的开销,并且我不确定JITer是否聪明到足以缓存此编译。 - Farhad Jabiyev

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