在我们将Lambda表达式指定给Expression<TDelegate>类型的变量(参数)时,编译器将会发出生成表达式目录树的指令,如上面这段代码中的Lambda表达式(a, b) => a * b + 2将创建一个表达式目录树,它表示的是一种数据结构,即我们把一行代码用数据结构的形式表示了出来,具体来说最终构造出来的表达式目录树形状如下图所示:
这里每一个节点都表示一个表达式,可能是一个二元运算,也可能是一个常量或者参数等,如上图中的ParameterExpression就是一个参数表达式,ConstantExpression是一个常量表达式,BinaryExpression是一个二元表达式。我们也可以在Visual Studio中使用Expression Tree Visualizer来查看该表达式目录树:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 4 .locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1< class [System.Core]System.Func`3<int32,int32,int32>> expression, [1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000, [2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001, [3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002) IL_0000: nop IL_0001: ldtoken [mscorlib]System.Int32 IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(...) IL_000b: ldstr "a" IL_0010: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter( class [mscorlib]System.Type,
IL_0038: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle() IL_003d: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object, class [mscorlib]System.Type) IL_0042: call class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.Expression) IL_0047: call class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::Add(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.Expression) IL_004c: ldc.i4.2 IL_004d: newarr [System.Core]System.Linq.Expressions.ParameterExpression }
我们编写的查询表达式,将封装为一种抽象的数据结构,这个数据结构就是表达式目录树,当我们在使用上面返回的值时,编译器将会以该值所期望的方式进行翻译,这种方式就是由Expression和Provider来决定。可以看到,这样将会非常的灵活且具有良好的可扩展性,有了表达式目录树,可以自由的编写自己的Provider,去查询我们希望的数据源。经常说LINQ为访问各种不同的数据源提供了一种统一的编程方式,其奥秘就在这里。然而需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,后面会说到这一点。
IEnumerable<String> query = from s in myList where s.StartsWith("a") select s;
foreach (String s in query) { Console.WriteLine(s); }
Console.Read(); }
为什么在LINQ to Objects中返回的是IEnumerable类型的数据而不是IQueryable呢? 在LINQ to Objects中查询表达式或者Lambda表达式并不翻译为表达式目录树,因为LINQ to Objects查询的都是实现了IEnmerable接口的数据,所以查询表达式或者Lambda表达式都可以直接转换为.NET代码来执行,无需再经过转换为表达式目录这一步,这也是LINQ to Objects比较特殊的地方,它不需要特定的LINQ Provider。我们可以看一下IEnumerable接口的实现,它里面并没有Expression和Provider这样的属性,如下图所示:
1 2 3 4
public interface IEnumerable<T>:Ienumerable { IEnumerator<T> GetEnumerator(); }
至于LINQ to Objects中所有的标准查询操作符都是通过扩展方法来实现的,它们在抽象类Enumerable中定义,如其中的Where扩展方法如下代码所示:
看到这里两组方法的参数,其实大家已经可以知道,Provider负责执行表达式目录树并返回结果。如果是LINQ to SQL的Provider,则它会负责把表达式目录树翻译为T-SQL语句并并传递给数据库服务器,并返回最后的执行的结果;如果是一个Web Service的Provider,则它会负责翻译表达式目录树并调用Web Service,最终返回结果。 这里四个方法其实就两个操作CreateQuery和Execute(分别有泛型和非泛型),CreateQuery方法用于构造一个 IQueryable 对象,该对象可计算指定表达式目录树所表示的查询,返回的结果是一个可枚举的类型,;而Execute执行指定表达式目录树所表示的查询,返回的结果是一个单一值。自定义一个最简单的LINQ Provider,至少需要实现IQueryable和IQueryProvider两个接口,在下篇文章中,你将看到一个综合的实例。
扩展LINQ的两种方式
通过前面的讲解,我们可以想到,对于LINQ的扩展有两种方式,一是借助于LINQ to Objects,如果我们所做的查询直接在.NET代码中执行,就可以实现IEnumerable接口,而无须再去实现IQueryable并编写自定义的LINQ Provider,如.NET中内置的List等。如我们可以编写一段简单自定义代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class MyData<T> : IEnumerable<T> where T : class { public IEnumerator<T> GetEnumerator() { return null; }
public class QueryableData<TData> : IQueryable<TData> { public QueryableData() { Provider = new TerryQueryProvider(); Expression = Expression.Constant(this); }
public QueryableData(TerryQueryProvider provider, Expression expression) { if (provider == null) { throw new ArgumentNullException("provider"); }
if (expression == null) { throw new ArgumentNullException("expression"); }
if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type)) { throw new ArgumentOutOfRangeException("expression"); }
Provider = provider; Expression = expression; }
public IQueryProvider Provider { get; private set; } public Expression Expression { get; private set; }
public Type ElementType { get { return typeof(TData); } }
public IEnumerator<TData> GetEnumerator() { return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator(); }