`
RednaxelaFX
  • 浏览: 3052879 次
  • 性别: 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来调试才行了,呜。回头再说吧。
分享到:
评论

相关推荐

    Python-一个lambda演算的解释器实现

    在Python中,我们可以用相对简单的代码实现一个Lambda演算的解释器,来理解和操作这种抽象的计算系统。 ### Lambda表达式 Lambda演算的核心是lambda表达式,它由三部分组成:变量、箭头(λ)和函数体。一个简单的...

    lambda演算学习

    在Lambda演算中,一个重要的概念是能够用Lambda表达式来定义各种数学对象和运算。 ##### 4.1 Lambda可定义的自然数 自然数可以通过Church编码来表示。例如,数字0可以表示为一个接受一个参数但返回空函数的函数: ...

    C#lambda表达式的使用

    Lambda 表达式的定义仅仅是定义一个匿名方法,最终会生成一个委托对象。外部变量的引用将被“捕获”到委托对象内部,将会伴随委托对象的整个生命周期。 高级应用 Lambda 表达式可以用于数据查询、事件处理、线程池...

    Java8的Lambda表达式

    在Java 8中,Lambda表达式主要用于那些只包含一个抽象方法的接口,即功能接口。 在Java 8之前,我们常常需要使用匿名内部类来实现功能接口,这样做会生成很多冗余的代码。例如,添加一个事件监听器通常需要使用匿名...

    2_Lambda表达式.zip

    Lambda表达式的主要目的是为了创建匿名函数,即没有名字的函数,它可以被当作一个值传递给方法或存储在变量中。在本资料"2_Lambda表达式.zip"中,我们可能将深入学习Lambda表达式的概念、语法以及在实际开发中的应用...

    将字符串转换为lambda表达式

    在编程领域,`lambda`表达式是一种简洁的创建匿名函数的方式,它允许我们在不定义完整函数的情况下使用函数。在Python中,`lambda`表达式特别常见,因为它们可以帮助我们快速编写简洁的一行函数。本文将深入探讨如何...

    Lambda表达式.zip

    Lambda表达式是Java编程语言中的一个关键特性,它在Java 8中被引入,极大地简化了函数式编程。Lambda表达式本质上是匿名函数,可以被用作方法参数或局部变量,使得代码更加简洁且易于理解。这篇教程是针对初学者准备...

    Lambda表达式.docx

    Lambda表达式本质上是一个匿名函数,允许你将函数作为一个值传递,这在处理函数式编程任务时尤其有用。下面我们将详细探讨Lambda表达式的核心概念、使用场景、对接口的要求以及函数式接口。 1. **Lambda表达式的...

    java8lambda表达式Demo

    Java 8 是一个重要的Java平台版本,因为它引入了许多新特性,其中最显著的就是Lambda表达式。Lambda表达式是函数式编程的关键元素,它允许我们以更简洁、更易读的方式编写代码,特别是在处理集合和并发任务时。在这...

    在C++中使用Lambda函数提高性能(小文档)

    在C++中,Lambda函数是一种特殊的匿名函数,它使得开发者能够在代码中的任何地方快速定义小块的代码,并将其作为参数传递,或直接作为函数调用。Lambda函数通常被用于需要函数对象的场景,比如在STL算法中。这种函数...

    Python-Lambdatoolkit是一个lambda命令行CLI

    Python-LambdaToolkit是一个专为Python开发者设计的命令行工具,它简化了AWS Lambda函数的开发、构建、调试、测试和部署流程。这个工具让开发者能够在本地环境中高效地操作Lambda函数,极大地提高了工作效率。 ### ...

    使用Lambda表达式查找指定字符

    这里,Lambda表达式`(ch =&gt; ch == targetChar)`被传递给`FirstOrDefault`方法,该方法会在数组中查找满足条件的第一个元素。如果没有找到,`FirstOrDefault`会返回默认值(对于字符类型是'\0')。 Lambda表达式的...

    lambda使用详解

    方法引用是指 lambda 表达式允许我们定义一个匿名方法,并允许我们以函数式接口的方式使用它。函数式编程是指使用 lambda 表达式来实现业务功能,它可以将业务功能简洁的实现。 lambda 表达式的主要用途是替代匿名...

    LAMBDA(MATLAB)

    MATLAB版本的LAMBDA算法为用户提供了一个高效且易于使用的工具,包含了源码和详细的说明手册。 **一、LAMBDA算法的核心概念** 1. **模糊度**:在GPS测量中,相位观测值是浮点数,包含一个整数部分(模糊度)和一个...

    Lambda教程

    这个语句Lambda接收一个参数`n`,并在控制台上打印一条消息。 4. **类型推断(Type Inference)** Lambda表达式中的类型通常不需要显式声明,编译器会根据上下文推断参数和返回值的类型。例如: ```csharp lst....

    LAMBDA3_模糊度固定_lambda3_GPSlambda_enemyqab_源码

    由于不同频率的电磁波在电离层中的传播速度不同,导致相位观测值存在差分相位,这个差分相位被称为电离层延迟的双频线性组合。通过计算这个组合,可以估算出一个模糊度因子,然后利用这个因子将浮点解转换为整数解,...

    Java 8 lambda表达式

    在上述示例中,`FuncInterface`也是一个自定义的函数式接口,包含一个抽象方法`abstractFun(int x)`和一个默认方法`normalFun()`。 3. **Lambda表达式的使用场景** - **作为方法参数**:例如,`arrL.forEach(n -&gt; ...

    Lambda表达式的例子

    在这个例子中,`(p1, p2) -&gt; p1.getAge() - p2.getAge()` 就是一个Lambda表达式,它定义了一个比较两个Person对象年龄的方法。这里的`p1, p2`是参数,`-&gt;`后面是方法体,即计算两个Person的年龄差值。 Lambda表达式...

    亚马逊 aws Lambda demo

    在这个"亚马逊AWS Lambda demo"中,我们将深入探讨如何使用Lambda构建一个简单的Maven项目。 首先,Lambda函数是AWS Lambda的核心组件,它是一小段可以响应特定事件的代码。在这个Maven demo中,我们可能有一个Java...

    C++ Lambda Story - From C++98 to C++20.pdf

    **C++ Lambda表达式**是C++编程语言中一个强大的特性,从C++11标准开始引入,到C++20标准进一步增强。Lambda表达式允许程序员在代码中定义匿名函数,即没有名称的函数,这极大地提高了代码的灵活性和可读性。本书...

Global site tag (gtag.js) - Google Analytics