这是真的,实际的签名是:
public object Execute(Expression expression)
public TResult Execute<TResult>(Expression expression)
但是,这并不意味着 TResult 将始终是单个元素!它是预期从表达式返回的类型。
另外,请注意,TResult 没有没有限制,甚至没有 'class' 或 'new()'。
当您的表达式是单数结果时,TResult 是 MyObject,例如 .FirstOrDefault()。但是,当您对查询进行.Avg() 时,TResult 也可以是double,当您的查询是普通的.Select.Where 时,它也可以是IEnumerable<MyObject>。
Proof(*) - 我刚刚在我的 Execute() 实现中设置了一个断点,并使用 Watches 对其进行了检查:
typeof(TResult).FullName "System.Collections.Generic.IEnumerable`1[[xxxxxx,xxxxx]]"
expression.Type.FullName "System.Linq.IQueryable`1[[xxxxxx,xxxxx]]"
我承认三个重载,一个object,一个TResult 和一个IEnumerable<TResult> 可能更具可读性。我认为他们没有将其中三个作为未来接口的扩展点。我可以想象将来他们想出了比IEnumerable 更强大的东西,然后他们需要添加另一个重载等等。这个接口很简单,可以处理任何类型。
哦,看,除了IEnumerable,我们现在还有IQueryable,所以它至少需要四个重载:)
证明标有 (*),因为我的 IQueryProvider 代码中有一个小错误/功能,它掩盖了 LINQ 的真实行为。
LINQ 确实只为个别情况调用泛型 Execute。这是一个捷径,一个优化。
对于所有其他情况,它... 根本不调用 Execute() 它
对于所有其他情况,LINQ 在您的自定义 IQueryable<> 实现上调用 .GetEnumerator,所发生的事情是由 .. 决定的,只是您在那里写的内容。我的意思是,假设您实际上提供了 IQueryable 的自定义实现。如果你不这样做会很奇怪 - 总共只有大约 15 行,与自定义提供程序的长度相比没有任何意义。
在我从中获得“证明”的项目中,我的实现如下所示:
public System.Collections.IEnumerator GetEnumerator()
{
return Provider.Execute<IEnumerable>( this.Expression ).GetEnumerator();
}
public IEnumerator<TOut> GetEnumerator()
{
return Provider.Execute<IEnumerable<TOut>>( this.Expression ).GetEnumerator();
}
当然,由于名称冲突,其中之一是显式的。请注意,为了获取枚举器,我实际上使用明确声明的 TResult 调用 Execute。这就是为什么在我的“证明”中出现了这些类型。
我认为你看到了“TResult = Single Element”的情况,因为你写了类似这样的东西:
public IEnumerator<TOut> GetEnumerator()
{
return Provider.Execute<TOut>( this.Expression ).GetEnumerator();
}
这确实使您的 Execute 实现无需选择,并且必须返回单个元素。恕我直言,这只是您代码中的一个错误。你可以像我上面的例子那样做,或者你可以简单地使用无类型的 Execute:
public System.Collections.IEnumerator GetEnumerator()
{
return ((IEnumerable)Provider.Execute( this.Expression )).GetEnumerator();
}
public IEnumerator<TOut> GetEnumerator()
{
return ((IEnumerable<TOut>)Provider.Execute( this.Expression )).GetEnumerator();
}
当然,您的 Execute 实现必须确保为此类查询返回正确的 IEnumerables!