`
RednaxelaFX
  • 浏览: 3016063 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

同一个ParameterExpression被用在不同嵌套层次的lambda里会怎样?

    博客分类:
  • DLR
阅读更多
今天写代码的时候不小心写错了几个地方,把同一个ParameterExpression用在内外两个Expression tree里了。结果很诡异,总之先记下来。

用LINQv1,测试这样的代码:
using System;
using Linq = System.Linq.Expressions;
using Ast = System.Linq.Expressions.Expression;

sealed class Program {
    static void Main(string[] args) {
        var x = Ast.Parameter(typeof(double), "x");
        var y = Ast.Parameter(typeof(double), "y");

        var expr = Ast.Lambda<Action<double, double>>(
            Ast.Invoke(
                Ast.Lambda<Action<double, double>>(
                    Ast.Call(
                        null,
                        typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) }),
                        Ast.Convert(
                            x,
                            typeof(object)
                        )
                    ),
                    new [] { x, y }
                ),
                new Linq.Expression[] {
                    y, x
                }
            ),
            new [] { x, y }
        );
        expr.Compile()(2, 5);
    }
}

结果无论在内层的lambda里我输出x还是y,得到的都是5。于是我就纳闷了……

糟就糟在,这个lambda没办法用普通C#的lambda表达式写出来,因为如果这样写:
Expression<Action<double, double>> expr
  = (x,y) => ((Action<double, double>)((x,y) => Console.WriteLine(x)))(y,x);

在C#里是不合法的:x和y在内层被重定义了,而C#不允许这样的重定义。

本来这个时候应该拿SOS来调试一下,看看到底Compile出了怎样的DynamicMethod。不过想着既然有IronPython的LINQv2,干脆拿源码来调试更方便。
于是引用IronPython里的Microsoft.Scripting.Core.dll,把测试代码改成:
using System;
using Dlr = Microsoft.Linq.Expressions;
using Ast = Microsoft.Linq.Expressions.Expression;

sealed class Program {
    static void Main(string[] args) {
        var x = Ast.Parameter(typeof(double), "x");
        var y = Ast.Parameter(typeof(double), "y");

        var expr = Ast.Lambda<Action<double, double>>(
            Ast.Invoke(
                Ast.Lambda<Action<double, double>>(
                    Ast.Call(
                        null,
                        typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) }),
                        Ast.Convert(
                            x,
                            typeof(object)
                        )
                    ),
                    new [] { x, y }
                ),
                new Dlr.Expression[] {
                    y, x
                }
            ),
            new [] { x, y }
        );
        expr.Compile()(2, 5);
    }
}

就是换了俩namespace。在第29行设上断点,然后跟到LambdaExpression.Compile() -> LambdaCompiler.Compile() -> LambdaCompiler.CreateDelegate(),在这里看到底生成了怎样的DynamicMethod。
结果看到外层的DynamicMethod是:Void lambda_method$1(Microsoft.Runtime.CompilerServices.Closure, Double, Double)
IL_0000: /* 02  |          */ ldarg.0    
IL_0001: /* 7b  | 04000002 */ ldfld      !"指定的转换无效。"!
IL_0006: /* 16  |          */ ldc.i4.0   
IL_0007: /* 9a  |          */ ldelem.ref 
IL_0008: /* 74  | 02000003 */ castclass  System.Reflection.Emit.DynamicMethod
IL_000d: /* d0  | 02000004 */ ldtoken    Microsoft.Action`2[System.Double,System.Double]/
IL_0012: /* 28  | 06000005 */ call       System.Type GetTypeFromHandle(System.RuntimeTypeHandle)/System.Type
IL_0017: /* 14  |          */ ldnull     
IL_0018: /* 6f  | 06000006 */ callvirt   System.Delegate CreateDelegate(System.Type, System.Object)/System.Reflection.Emit.DynamicMethod
IL_001d: /* 74  | 02000007 */ castclass  Microsoft.Action`2[System.Double,System.Double]
IL_0022: /* 04  |          */ ldarg.2    
IL_0023: /* 03  |          */ ldarg.1    
IL_0024: /* 6f  | 06000008 */ callvirt   Void Invoke(Double, Double)/Microsoft.Action`2[System.Double,System.Double]
IL_0029: /* 2a  |          */ ret

其中调用的内层DynamicMethod是:Void lambda_method$2(Microsoft.Runtime.CompilerServices.Closure, Double, Double)
IL_0000: /* 03  |          */ ldarg.1    
IL_0001: /* 8c  | 02000002 */ box        System.Double
IL_0006: /* 28  | 06000003 */ call       Void WriteLine(System.Object)/System.Console
IL_000b: /* 2a  |          */ ret


一看,这不就对了么?编译出来的应该跟这个lambda表达式的一样才对啊:
Expression<Action<double, double>> expr
  = (x,y) => ((Action<double, double>)((m,n) => Console.WriteLine(m)))(y,x);

然后离开单步模式,直接运行下去,发现确实跟我原本预期的行为是一样的。把内层的x换成y也没错:内层是x的时候看到输出是5,内层是y的时候看到输出是2。

也就是说LINQv1和LINQv2中ParameterExpression的语义有了很大的变化,影响到了原本LINQv1中的Expression tree的语义……

那LINQv1到底是怎么回事呢?还真是得拿SOS来调试才行了,呜。回头再说吧。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics