LINQ 有两个版本。一个用于 IEnumerable 序列,一个用于 IQueryable 序列。
IEnumerable 版本是最简单的。那只是使用正确的参数调用正确的扩展函数。 The reference source code can be found here.
既然您在谈论查询,我假设您想知道 IQueryable 是如何做到这一点的。
实现 IEnumerable 的 Queryable 对象和实现相同接口的序列类之间的区别在于,Queryable 对象包含一个表达式和一个可以计算该表达式的提供者。 IQueryable 的扩展函数(如 Where 和 Select)知道如何更改表达式以使其成为选择 LINQ 语句指定的项目的表达式。
由于它们是 IQueryable 类的扩展函数,因此实现 IQueryable 的人不必提供 Where / Select / GroupBy 等功能。因此可以断言实现者已生成正确的表达式。
困难的部分不是为您的类实现 IQueryable,而是实现 IQueryable.Provider 返回的 Provider 对象。
但在详细介绍提供者之前,让我们创建实现 IQueryable 的类:
对于这个解释,我使用了以下来源
假设我有一个类Word(正如你所期望的一个Word,就像一个没有空格的字符串)和一个实现IQueryable<Word>的类WordCollection。由于WordCollection 实现了IQueryable<Word>,它还实现了IQueryable、IEnumerable<Word> 和IEnumerable。
class MyWordCollection : IQueryable<Word>, IQueryable,
IEnumerable<Word>, IEnumerable
{
public Type ElementType { get { return typeof(Word); } }
public IQueryProvider Provider { get; private set; }
public Expression Expression { get; private set; }
#region enumerators
public IEnumerator<Word> GetEnumerator()
{
return (Provider.Execute<IEnumerable<Word>>
(Expression)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion enumerators
}
注意IQueryable 和Provider 之间的区别。
-
IQueryable 是一个可以在其中查询元素的对象。 IQueryable 不必保存这些元素,但它知道谁可以提供它们。
- 提供者是可以提供查询对象的人,前提是您给他一个
Expression 以选择要提供的对象。
提供者可以自己保存这些元素,也可以从其他地方获取这些元素,例如从数据库中获取这些元素,或者形成一个文件,从互联网下载数据,或者只是在其中保存这些元素的序列。
由于您隐藏了 Provider 获取数据的位置,您可以将其与提供相同类型元素的其他 Provider 交换,但从其他来源获取它们。
在类中我们看到实现IQueryable<Word>和IQueryable的三个属性:
-
ElementType返回查询的类型:查询时,对象将返回Word的序列
-
Expression 保存由 LINQ 扩展函数计算的表达式
-
Provider 持有可以解释表达式并返回 ElementType 的对象序列的对象,MyWordCollection 承诺返回
后两个函数实现IEnumerable<Word>和IEnumerable。他们只是命令提供者计算表达式。结果是内存中的 Enumerable 序列。他们返回这个序列的 GetEnumerator()
ElementType 已实现。所以我们所要做的就是填写Expression 和Provider。我们可以在构造函数中这样做:
public WordCollection(IQueryProvider provider, Expression expression)
{
this.Provider = provider;
this.Expression = expression;
}
此构造函数允许WordCollection 的用户将任何Provider 放入其中,例如从文件中获取Words 的Provider,或从互联网获取其文字的Provider等。
让我们看看Provider。 Provider 必须实现IQueryProvider,它有四个函数:IQueryable<Word> 的两个通用函数和IQueryable 的两个非通用函数。
其中一个功能是,给定一个表达式,返回一个可以执行该表达式的IQueryable。另一个函数 finally 将解释表达式并返回请求的项目
作为一个例子,我有一个单词提供者,它代表了威廉莎士比亚十四行诗中所有单词的集合
internal class ShakespeareDownloader
{
private const string sonnetsShakespeare = "http://www.gutenberg.org/cache/epub/1041/pg1041.txt";
public string DownloadSonnets()
{
string bookShakespeareSonnets = null;
using (var downloader = new WebClient())
{
bookShakespeareSonnets = downloader.DownloadString(sonnetsShakespeare);
}
return bookShakespeareSonnets;
}
}
所以现在我们有了所有单词的来源,我可以创建一个单词提供者:
class WordProvider : IQueryyProvider
{
private IQueryable<Word> allSonnetWords = null;
private IQueryable<Word> GetAllSonnetWords()
{
if (allSonnetWords == null)
{
string sonnets = shakespeareDownLoader.DownloadSonnets();
// split on whitespace:
string[] sonnetWords = sonnets.Split((char[])null,
StringSplitOptions.RemoveEmptyEntries);
this.allSonnetWords = sonnetWords
.Select(word => new Word() { Text = word })
.AsQueryable<Word>();
}
}
好的,现在我们有了一些可以为我们提供文字的东西。我们可以这样写:
IQueryable<Word> allWords = new WordCollection();
如上所示,这将构造一个带有表达式的WordProvider,以获取所有Words。一旦执行查询,就会调用 WordProvider 的 CreateQuery。它所要做的就是为这个WordProvider 和给定的表达式创建一个新的WordCollection:
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new WordCollection(this, expression) as IQueryable<TElement>;
}
一段时间后,集合成为枚举器,并调用WordCollection.GetEnumerator()。如上所示,这将要求提供者使用表达式进行枚举,从而调用WordProvider.Execute<TResult>(Expression)
在 WordCollection 和 WordProvider 之间的所有这些反复之后,提供者终于可以开始执行表达式了。
public TResult Execute<TResult>(Expression expression)
{
}
虽然我的提供者只能提供单词序列,但是这个函数是一个通用函数。执行的结果要么是一个单词序列,要么是一个单词(例如,当使用 First() 或 Max() 时)。如果它要求其他我无法提供的东西,则允许例外。
该函数被声明为通用函数,以防我是一个提供者,它还可以提供单词以外的其他东西,比如句子。
所以 Execute 函数应该解释 Expression 并返回一个 TResult,它应该是一个 Word 序列或一个 Word。
我们要做的第一件事是获取完整的十四行诗词集合,并确定是请求序列还是单个词:
public TResult Execute<TResult>(Expression expression)
{
IQueryable<Word> allSonnetWords = this.GetAllSonnetWords();
var sequenceRequested = typeof(TResult).Name == "IEnumerable`1";
到目前为止,所有代码都相当简单。但现在困难的部分是解释表达式以找出返回的内容。
幸运的是,在这个例子中,allSonnetWords 的集合也是 IQueryable。我们必须稍微更改表达式,以便它可以在 IQueryable 上工作,而不是在 WordProvider 上工作。
这可以通过 ExpressionVisitor 的派生类来完成:
class WordExpressionModifier : System.Linq.Expression.ExpressionVisitor
{
public ExpressionTreeModifier(IQueryable<Word> allWords)
{
this.allWords = allWords;
}
private readonly IQueryable<Word> allWords;
protected override Expression VisitConstant(ConstantExpression c)
{
// if the type of the constant expression is a WordCollection
// return the same expression for allWords
if (c.Type == typeof(WordCollection))
return Expression.Constant(allWords);
else
return c;
}
所以现在我们可以完成 Execute 函数了
public TResult Execute<TResult>(Expression expression)
{
IQueryable<Word> allSonnetWords = this.GetAllSonnetWords();
var sequenceRequested = typeof(TResult).Name == "IEnumerable`1";
// replace the expression for a WordCollection to an expression for IQueryable<Word>
var expressionModifier = new ExpressionTreeModifier(allSonnetWords);
Expression modifiedExpression = expressionModifier.Visit(expression);
TResult result;
if (isEnumerable)
// A sequence is requested. Return a query on allSonnetWords provider
result = (TResult)allSonnetWords.Provider.CreateQuery(modifiedExpression);
else
// a single element is requested. Execute the query on the allSonnetWords.provider
result = (TResult)allSonnetWords.Provider.Execute(modifiedExpression);
return result;
}
现在所有工作都完成了,我们可以试试:
static void Main(string[] args)
{
IQueryable<Word> allWords = new WordCollection();
IQueryable<Word> Bwords = allWords
.Where(word => word.Text.FirstOrDefault() == 'b')
.Take(40);
foreach (var word in Bwords)
{
Console.WriteLine(word.Text);
}
}