zoukankan      html  css  js  c++  java
  • [原译]Lambda高手之路第二部分

    闭包的影响

    为了展示闭包的影响,我们看下面这个例子。

    var buttons = new Button[10];
    
    for(var i = 0; i < buttons.Length; i++)
    {
        var button = new Button();
        button.Text = (i + 1) + ". Button - Click for Index!";
        button.OnClick += (s, e) => { Messagebox.Show(i.ToString()); };
        buttons[i] = button;
    }
    //如果我们点击按钮会发生什么

    这个问题很怪,我在我的JavaScript课程上经常问我的学生。95%的学生会说。显然按钮0显示0,按钮1显示1,等等。而不足5%的学生学习了闭包之后会明白。所有的按钮都会显示10.

    局部变量i的值改变了。并且等于buttons.Length。也就是10了。想要避免这个诡异的情况也很简单。如下就行了。

    var button = new Button();
    var index = i;
    button.Text = (i + 1) + ". Button - Click for Index!";
    button.OnClick += (s, e) => { Messagebox.Show(index.ToString()); };
    buttons[i] = button;

    问题解决了,但是index变量是一个值类型,因此保留了“全局”i的一个拷贝

    最后一个话题是一个叫做表达式树的东西,他和Lambda表达式协作。并且,他会使得在ASP.NET MVC中的Html扩展方法发生一些很奇妙的东西。关键的问题是:如何发现目标函数

    1. 传递进去的变量的名称是什么
    2. 方法的主体是什么
    3. 函数体里使用了什么类型

    Expression 解决了这些问题,他允许我们挖掘生成的表达式树,我们也可以执行传递给Func和Action委托的函数,而且,可以在运行时解析Lambda表达式

    我们看一下如何使用Expression 类型的例子

    Expression<Func<MyModel, int>> expr = model => model.MyProperty;
    var member = expr.Body as MemberExpression;
    var propertyName = memberExpression.Member.Name; //当 member != null的时候执行  ...

    这是Expression最简单的用法了。规则也相当直接。通过构建一个Expression类型的对象。编译器为生成的解释树生成一些元数据。这个解释树包含所有相关的信息,比如参数和方法体。

    方法体包含完整的解释树,我们可以访问这些操作。就像完整的语句一样。也可以操作返回指和类型。当然,返回可以为null,无论如此。大多数情况下。我们对表达式很感兴趣。这和ASP.NET MVC中处理Expression类型的方法是很相似的—可以得到使用的参数的名字,而好处是显而易见的。不会出现名称拼写错误了。也就不会出现因此而出现的编译错误了。

    注意:当程序员仅仅对调用的属性的名字感兴趣的时候。有一个更简单,更优雅的解决方案,那就是参数特性CallerMemberName 这个特性可以得到调用方法/属性的名称。而字段被编译器自动填充。因此,如果我们仅仅想知道名字,而不想知道其他更多的信息,那么我我们只需要写出像下面这个例子里的代码就行了。通过调用WhatsMyName() 方法可以返回方法的名字

    string WhatsMyName([CallerMemberName] string callingName = null)
    {
        return callingName;
    }

    Lambda表达式的性能

    有一个很大的问题:Lambda表达式有多快?好吧。首先,我们期望他们能和我们一般的函数一样快。下一节里。我们会看到Lambda的MSIL代码和普通的函数没有太大的不同。

    最有趣的一个讨论是如果Lambda表达式产生了闭包,将会和使用全局变量的方法一样快。这就产生了一个有趣的问题,和一个区域内局部变量的数目多少会有关系吗?

    我们看看测试代码,通过4个不同的指标,我们可以看到普通方法和Lambda方法的不同。

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    
    namespace LambdaTests
    {
        class StandardBenchmark : Benchmark
        {
            const int LENGTH = 100000;
            static double[] A;
            static double[] B;
    
            static void Init()
            {
                var r = new Random();
                A = new double[LENGTH];
                B = new double[LENGTH];
    
                for (var i = 0; i < LENGTH; i++)
                {
                    A[i] = r.NextDouble();
                    B[i] = r.NextDouble();
                }
            }
    
            static long LambdaBenchmark()
            {
                Func<double> Perform = () =>
                {
                    var sum = 0.0;
    
                    for (var i = 0; i < LENGTH; i++)
                        sum += A[i] * B[i];
    
                    return sum;
                };
                var iterations = new double[100];
                var timing = new Stopwatch();
                timing.Start();
    
                for (var j = 0; j < iterations.Length; j++)
                    iterations[j] = Perform();
    
                timing.Stop();
                Console.WriteLine("Time for Lambda-Benchmark: \t {0}ms", timing.ElapsedMilliseconds);
                return timing.ElapsedMilliseconds;
            }
    
            static long NormalBenchmark()
            {
                var iterations = new double[100];
                var timing = new Stopwatch();
                timing.Start();
    
                for (var j = 0; j < iterations.Length; j++)
                    iterations[j] = NormalPerform();
    
                timing.Stop();
                Console.WriteLine("Time for Normal-Benchmark: \t {0}ms", timing.ElapsedMilliseconds);
                return timing.ElapsedMilliseconds;
            }
    
            static double NormalPerform()
            {
                var sum = 0.0;
    
                for (var i = 0; i < LENGTH; i++)
                    sum += A[i] * B[i];
    
                return sum;
            }
        }
    }

    本来用Lambda表达式我们可以把代码写的更好。最终我没有这样做。是为了防止影响看最后的结果。因此,本质上。上述代码里有三个方法
    1个是Lambda测试,一个是普通测试,还有一个是在普通测试里调用的方法。而没有的第四个方法其实是我们的lambda表达式。在第一个方法里已经创建了。计算过程很简单,我们随机取数字防止编译器做任何优化。最后我们对普通方法和Lambda方法的不同很有兴趣。

    如果我们运行这个测试程序。我们会发现Lambda表达式并不总是比普通方法差。我们惊奇的是有时候甚至Lambda表达式更快一些。然而,在有闭包的情况下,,就不对了。这就是说大多数情况下我们可以毫不犹豫的使用lambda表达式。当使用闭包的时候会出现一点点的性能损失,不过还好,下一节会讲述性能损失的几个原因。
    下面是测试输出的结果表

    Test Lambda [ms] Normal [ms]
    0 45+-1                       46+-1
    1 44+-1                       46+-2
    2 49+-3                      45+-2
    3 48+-2                      45+-2

    下图是上表的结果,我们可以看得到普通的方法和Lambda表达式基本上限差不多。当使用Lambda表达式的时候没有太大的性能损失。

    第三部分会介绍背后的秘密-MSIL

  • 相关阅读:
    F#周报2019年第33期
    The .NET World——gPRC概览
    编程杂谈——Non-breaking space
    F#周报2019年第32期
    F#周报2019年第31期
    F#周报2019年第30期
    pat 乙级 1015. 德才论 (25) c++
    pat 乙级 1008. 数组元素循环右移问题 (20)
    PAT 乙级 1007. 素数对猜想 (20) c++ 筛选法求素数
    PAT-B 1005. 继续(3n+1)猜想 (25) c++
  • 原文地址:https://www.cnblogs.com/lazycoding/p/2847579.html
Copyright © 2011-2022 走看看