手工把LINQ转成表达式(三) from

17 篇文章 0 订阅
10 篇文章 0 订阅

先来看这段查询

            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,以让后面的解析方法继续使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值