zoukankan      html  css  js  c++  java
  • 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语言一样解析,如果遇到老版本的代码,一定要特别注意

  • 相关阅读:
    POJ 1426 Find The Multiple(数论——中国同余定理)
    POJ 2253 Frogger(Dijkstra变形——最短路径最大权值)
    POJ 3790 最短路径问题(Dijkstra变形——最短路径双重最小权值)
    POJ 3278 Catch That Cow(模板——BFS)
    HDU 1071 The area
    HDU 1213 How Many Tables(模板——并查集)
    POJ 1611 The Suspects
    light oj 1214 Large Division
    POJ 1258 Agri-Net(Prim算法求解MST)
    POJ 2387 Til the Cows Come Home(模板——Dijkstra算法)
  • 原文地址:https://www.cnblogs.com/wujuncheng/p/13452020.html
Copyright © 2011-2022 走看看