• Lambda表达式


    Lambda表达式

    Lambada表达式是一种可以替代委托实例的匿名方法。编译器会立即将Lambda表达式转换为一下两种形式之一:

    • 一个委托实例
    • 一个类型为Expression的表达式树(这个后面将)

    匿名方法

    上面说Lambada是一种匿名方法,那么就要先了解一下什么是匿名方法

    匿名方法是C#2.0引入的特性

    匿名方法的写法实在delegate关键字后面跟上参数的声明(可选),然后是方法体

    using System;
    class Program
    {
        delegate void Example();
        static void Main(string[] args)
        {
            // Example e = delegate(){ Console.WriteLine("一个匿名方法的实现"); };
            // 如果没有参数,可以省略参数的括号
            Example e = delegate { Console.WriteLine("一个匿名方法的实现"); };
            e();
        }
    }
    

    这里声明了一个匿名方法,匿名方法解决的问题,是有时候想用委托,就必须需要一个方法,但是这个方法只是在这个委托中使用一下子,不需要在其他地方复用,于是引入了匿名方法

    delegate(){ Console.WriteLine("一个匿名方法的实现"); };
    

    匿名方法的写法其实与普通方法并无异样,只是用delegate关键字在前标注,省略掉方法名(如果不好理解,可以理解成方法名为delegate,没有参数可以省略括号,只能用于注册进委托)

    匿名方法使用情况不多,因为C#3.0引入的Lambda更加强大,也是后面要讲的重点

    匿名方法目前最广泛的用法,是用于声明空事件处理器的事件

    public event EventHandler Clicked =delegate {  };
    

    Clicked事件不会进行任何操作,因为没有定义任何操作,但是Clicked不为空,不会抛异常,在用户层面,就是点击了某个按钮后没有任何变化,但是如果Clicked为空,就会抛异常

    完全省略参数的声明是匿名方法独有的特性,即使委托需要这些参数声明,如上面声明空事件处理器的事件,EventHandler其实需要一个object的参数和一个EventArgs类型的参数

    Lambda表达式

    Lambda是一种更强大匿名方法,前面讲了匿名方法,先来看看Lambda如何替代匿名方法

    using System;
    class Program
    {
        delegate void Example();
        static void Main(string[] args)
        {
            // Example e = () => { Console.WriteLine("一个Lambda表达式"); };
            // 当方法体只有一句时可以省略大括号
            Example e = () =>  Console.WriteLine("一个Lambda表达式"); 
            e();
        }
    }
    

    从代码中可以看到,匿名方法被替换成了这样一句

    () => { Console.WriteLine("一个Lambda表达式"); }
    

    在Lambda表达式中=>之前的是方法的参数,=>之后是方法体

    参数和方法体的编写规则
    • 编译器通常可以根据上下文推断出Lambda表达式的类型,但是当无法推断的时候则必须显式指定每一个参数的类型
    // 能够推断参数类型
    (x) => { return x; }
    // 不能推断参数类型
    (int x) => { return x;}
    
    • 没有参数,一个参数和多个参数时的写法
    // 没有参数时小括号不能省略
    () => { Console.WriteLine("一个Lambda表达式"); }
    
    (x) => { return x; }
    // 只有一个参数时可以省略小括号
    x => { return x; }
    
    // 多个参数时小括号不能省略
    (x,y,z) => { return x+y+z; }
    
    • 方法体只有一条语句的时候,可以省略大括号,return也可以省略;方法体有多条语句时大括号不能省略
    x => { return x; }
    // 一条语句可以省略大括号
    x => return x;
    // 一条语句可以省略return
    x => x;
    
    // 多条语句时不能省略大括号和return
    x =>
    {
        Console.WriteLine("看看");
        return x;
    }; 
    

    Lambda表达式的闭包和foreach

    Lambda表达式可以引用方法内定义的局部变量和方法的参数(外部变量)

    Lambda表达式所引用的外部变量称为捕获变量,捕获变量的表达式称为闭包

    using System;
    class Program
    {
        static void Main(string[] args)
        {
            int x = 2;
            Func<int, int> sum = n => n + x;
            Console.WriteLine(sum(10));    // 输出12
        }
    }
    

    在这个例子中,x就是被捕获的变量

    捕获变量的值

    Lambda表达式捕获的变量是在调用委托时赋值,而不是在捕获时赋值

    using System;
    class Program
    {
        static void Main(string[] args)
        {
            int x = 2;
            // 捕获外部变量x,但此时并没有赋值
            Func<int, int> sum = n => n + x;
            x = 10;
            // 调用个委托时才赋值,此时x是10
            Console.WriteLine(sum(10));    // 输出20
        }
    }
    

    捕获变量的生命周期会延伸到和委托的生命周期一致

    Lambda表达式foreach的两个版本

    如果Lambda捕获迭代变量,最后会有怎样的结果

    using System;
    class Program
    {
        static void Main(string[] args)
        {
            Action[] actions = new Action[3];
            for (int i = 0; i < 3; i++)
            {
                actions[i] = () => Console.WriteLine(i);
            }
    
            foreach (var a in actions)
            {
                a();
            }
            // 输出333
        }
    }
    

    先来看这个例子,利用Lambda表达式捕获了for循环的i变量,但此时i的值并没有确定,前面说过,Lambda捕获的变量在调用时才赋值,所以这虽然捕获了三次i,但这三次都是捕获的同一个i,所以最后在调用时赋值了i的最后的值3(前面说过,捕获变量的生命周期会延伸到和委托的生命周期一致,虽然for循环结束了,但是因为Lambda的捕获延长了生命周期,3这个值保留了下来),所以最后输出的是333

    如果要解决这个问题,只需要将循环变量指定到内部的变量中,即

    using System;
    class Program
    {
        static void Main(string[] args)
        {
            Action[] actions = new Action[3];
            for (int i = 0; i < 3; i++)
            {
                int temp = i;
                actions[i] = () => Console.WriteLine(temp);
            }
    
            foreach (var a in actions)
            {
                a();
            }
            // 输出012
        }
    }
    

    对于lambda表达式来说,捕获了三次temp,但是每一次都是新定义的temp,所以不受影响

    下面来看一个foreach的“BUG"

    using System;
    
    class Program
    {
        static void Main(string[] args)
        {
            Action[] actions = new Action[3];
            int i = 0;
            foreach (char c in "abc")
            {
                actions[i++] = () => Console.WriteLine(c);
            }
    
            foreach (Action action in actions)
            {
                action();
            }
            // 输出abc
        }
    }
    

    这里输出结果是abc,这是因为foreach的每一个迭代变量都是不可变的,所以可以理解为循坏体中的局部变量,也就是类似于上面的temp,但是,在C#5.0之前,结果并不是这样的,foreach会像前面的for语言一样解析,如果遇到老版本的代码,一定要特别注意

  • 相关阅读:
    各操作系统各文件系统支持的最大文件的大小
    Java调用百度地图API
    Java面试宝典(3)Java基础部分
    Java7中的try-with-resources
    Spring学习笔记(6)——IoC的三种注入方式
    cmd中java的编译命令——java和javac、javap
    Spring学习笔记(14)——注解零配置
    java中多种方式解析xml
    双三次插值
    RCNN到faster RCNN 简介
  • 原文地址:https://www.cnblogs.com/wujuncheng/p/13452020.html
Copyright © 2020-2023  润新知