先来看这段查询
int[] array = { 1, 3, 3 };
IQueryable<int> A = array.AsQueryable();
IQueryable<int> B = array.AsQueryable();
var q = from a in A
from b in B
select new { a, b};
编译后q的表达式为
System.Int32[].SelectMany(a => value(Test.Program+<>c__DisplayClass1).B, (a, b) => new <>f__AnonymousType0`2(a = a, b = b))
可以看到在有多个from情况下调用的方法是SelectMany,从另一个角度说就是不考虑第一个from的情况下from对应的方法就是SelectMany,在解析的时候只要碰到from语句就可以作如下转换:
调用SelectMany,把linqContext中的前一个表达式作为调用方,第一个参数Func<T, TResult>中的T为前一个linqContext的输入参数,TResult则是当前语句in后的变量,第二个参数Func<T1, T2, TResult>的T1还是前一个输入参数,T2是from后的参数也就是当前参数,TResult则是把2个输入参数都返回。
有一点要稍微说明下假设语句是
from a in A
from b in B
select new { a.i, b.j, b.k }
这种情况下,我们解析转换的时候SelectMany的第二个参数返回的也是a, b,最后再调用一次Select来选出真正的结果,而不是在SelectMany内一次全部做掉,这样处理是为了统一简化操作,也就是说最后表达式正确但冗余,目前先保证正确冗余等到谈优化的时候再处理。
from的具体转换代码:
case "query_from_expression":
var isFirst = _linqVariables.Count == 0;
if (!isFirst)
EnterScope(new[] { _linqVariables.Peek().NextParameter });
var source = ProcessExpression(expNode.LastChild);
if (!isFirst)
LeaveScope();
var parameterType = source.GetElementType();
var parameterName = expNode.GetChild("anonymous_function_parameter_decl").FindTokenAndGetText();
var parameter = Expression.Parameter(parameterType, parameterName);
if (isFirst)
{
_linqVariables.Push(new LINQContext
{
Parameter = parameter,
Name = GetTransparentIdentifier(),
IsFirst = true,
Self = source
});
}
else
{
var prev = _linqVariables.Peek();
var body = GetAnonymousExpression(
new[] { prev.NextParameter.Name, parameterName },
new[] { prev.NextParameter.Type, parameterType },
new[] {prev.NextParameter, parameter}
);
var name = GetTransparentIdentifier();
var from = Expression.Call(MakeMethod(op, prev.NextParameter.Type, parameterType, body.Type),
prev.Self,
Expression.Lambda(Expression.Convert(source, typeof(IEnumerable<>).MakeGenericType(parameterType)), prev.NextParameter),
Expression.Lambda(body, prev.NextParameter, parameter)
);
_linqVariables.Push(new LINQContext
{
Parameter = parameter,
Name = name,
Self = from
});
}
break;
linqContext的IsFirst就是为了识别是否为第一个from来更好的生成表达式。为了能让之前的表达式分析程序正确处理linq语句产生的别名变量,在linq处理代码中,每次调用之前的ProcessExpression都要先EnterScope把linqcontext上最后一个参数作为lambda表达式的参数传入,这样之后的lambda表达式处理的时候才不会出现无法识别的变量名这种情况。
GetTransparentIdentifier是给每个linqcontext产生一个内部别名,以保证整个表达式参数命名前后连贯一致,不会相互混淆:
private const string TransparentIdentifier = "<>h__TransparentIdentifier";
private string GetTransparentIdentifier()
{
return String.Format("{1}{0}", _linqVariables.Count, TransparentIdentifier);
}
命名采用了C#编译器默认的命名风格,保证不会和用户自己定义的变量名混淆。
所有LINQ语句解析最后段都会保存当前处理的linqcontext,以让后面的解析方法继续使用。