16.lambda表达式和闭包

16.委托和事件-lambda表达式


16.1 知识点

什么是lambda表达式

可以将lambda表达式理解为匿名函数的简写。它除了写法不同外,使用上和匿名函数一模一样,都是和委托或者事件配合使用的。

lambda表达式语法

// lambda表达式
// (参数列表) =>
// {
//     // 函数体
// };

// 匿名函数
// delegate (参数列表)
// {

// };

lambda表达式的使用

主函数内

无参无返回值的lambda表达式

Action action1 = () =>
{
    Console.WriteLine("无参无返回值的lambda表达式");
};
action1(); // 无参无返回值的lambda表达式

有参无返回值的lambda表达式

Action<int> action2 = (int value) =>
{
    Console.WriteLine("有参无返回值的lambda表达式 int value:{0}", value);
};
action2(100); // 有参无返回值的lambda表达式 int value:100

有参无返回值的lambda表达式省略参数类型的写法

参数类型可以省略,参数类型和委托或事件容器一致。

Action<int> action3 = (value) =>
{
    Console.WriteLine("省略参数类型的写法 value:{0}", value);
};
action3(200); // 省略参数类型的写法 value:200

有参有返回值的lambda表达式

Func<string, int> action4 = (value) =>
{
    Console.WriteLine("有参有返回值的lambda表达式 value:{0}", value);
    return 1;
};
Console.WriteLine(action4("123123"));
// 有参有返回值的lambda表达式 value:123123
// 1
// 其它传参使用等和匿名函数一样
// 缺点也是和匿名函数一样的,添加到委托后没有办法指定移除lambda表达式

闭包

闭包的基本概念

  • 闭包是指一个函数(或者委托)以及其相关的引用环境,形成了一个封闭的作用域。闭包能够捕获并维持其所在作用域的状态,即使在该作用域外部调用闭包,它也能够访问和修改这些状态。
  • 重点:内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。
  • 注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
  • 在 C# 中定义一个函数,而在这个函数内部又使用了外部的变量时,这个函数就可以形成一个闭包。闭包简单来说就是一个函数连同它用到的外部变量一起打包了起来。
  • 让我们通过一个通俗易懂的例子来说明闭包:
using System;

class Program
{
    static Func<int, int> Counter(int increment)
    {
        int count = 0; // 外部变量
        Func<int, int> incrementCounter = (number) =>
        {
            count += increment; // 在函数内部使用了外部变量
            return number + count;
        };
        return incrementCounter;
    }

    static void Main(string[] args)
    {
        // 创建一个计数器,增量为 5
        var counter = Counter(5);

        // 使用计数器,初始数字为 10
        Console.WriteLine(counter(10)); // 输出 15

        // 再次使用计数器,初始数字为 20
        Console.WriteLine(counter(20)); // 输出 25
    }
}
  • 在这个例子中,Counter 函数返回了一个委托(incrementCounter),这个委托内部引用了外部的变量 count。虽然 Counter 函数已经执行完毕,但是 incrementCounter 依然可以访问并修改 Counter 函数中的 count 变量。这就是闭包的作用,它保留了函数定义时的环境,使得函数在之后的执行中能够访问和修改它所引用的外部变量。

  • 在这个例子中,闭包是在 Counter 函数内部定义的匿名方法,即 incrementCounter。这个匿名方法捕获了 Counter 函数中的局部变量 count,并形成了闭包。因此,无论 Counter 函数执行完毕后,返回的委托 incrementCounter 仍然可以访问和修改 count 变量,这就是闭包的特性。

class语句块外 namespace语句块内

// 测试类
class Test
{
    // 事件
    public event Action myEvent;

    public Test()
    {
        // 构造函数的临时变量 正常来说构造函数结束他就会被释放
        int value = 10;

        // 这里就形成了闭包
        // 因为当构造函数执行完毕时,其中申明的临时变量value的声明周期被改变了
        // value还有作用不会被释放,一直会存到action里面,除非手动把action置空
        myEvent = () =>
        {
            Console.WriteLine(value);
        };

        for (int i = 0; i < 10; i++)
        {
            // 注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

            // 此index 非彼index 每次for循环的index都是新的临时变量
            int index = i;

            myEvent += () =>
            {
                Console.WriteLine("i:" + i);
                // 这里的会一直会输出 i:10
                // 本质上传的是i变量,这个i变量在事件没有执行时一直没用到,一直存在事件容器里
                // 但他真正用的时候,这个i的最终值已经是10了,所以只会输出 i:10
                // i在父函数范围内(Test这个函数的语句块)的最终值是10

                Console.WriteLine("index:" + index);
                // 这里会逐个输出index:0-9
                // 因为每次for循环的index都是新的临时变量,所以一共会有10个index临时变量
                // 所以保存的index都不同
                // index在父函数范围内(每一次for循环的语句块)的最终值就是int index = i时的赋值
            };
        }
    }

    // 执行事件
    public void DoEvent()
    {
        myEvent();
    }
}

i 的值为 10:

  • i 是 for 循环中的变量,在每次循环迭代时都会被更新。但是在匿名方法中,i 是通过闭包捕获的,它捕获的是变量 i 的引用,而不是值的拷贝。
  • myEvent 被调用时,所有被捕获的 i 都指向同一个内存位置,它们都引用了最后一次迭代的值,也就是 10
  • 因此,每次调用 myEvent 时,打印的 i 的值都是 10

index 的值为每次迭代的值:

  • 在每次循环迭代时,会为 index 创建一个新的局部变量,并在匿名方法中捕获了该变量的值。
  • 每个匿名方法捕获的 index 都是不同的变量实例,它们具有不同的内存位置和值。
  • 因此,每次调用 myEvent 时,打印的 index 的值会是对应迭代时的值,即 09

主函数内

// 调用构造函数,临时变量value的生命周期被改变了
Test test = new Test(); // 10

test.DoEvent();
// i: 10
// index: 0
// i: 10
// index: 1
// i: 10
// index: 2
// i: 10
// index: 3
// i: 10
// index: 4
// i: 10
// index: 5
// i: 10
// index: 6
// i: 10
// index: 7
// i: 10
// index: 8
// i: 10
// index: 9

总结

匿名函数的特殊写法就是lambda表达式。固定写法是(参数列表)=>{},参数列表可以直接省略参数类型。主要在委托传递和存储时,为了方便可以直接使用匿名函数或者lambda表达式。缺点是无法指定移除。


16.2 知识点代码

using System;

namespace Lesson15_lambda表达式
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("lambda表达式");

            #region 知识点一 什么是lambda表达式
            //可以将lambda表达式 理解为匿名函数的简写
            //它除了写法不同外
            //使用上和匿名函数一模一样
            //都是和委托或者事件 配合使用的
            #endregion

            #region 知识点二 lambda表达式语法

            //lambda表达式
            //(参数列表) =>
            //{
            //    //函数体
            //};

            //匿名函数
            //delegate (参数列表)
            //{

            //};


            #endregion

            //主函数内

            #region 知识点三 lambda表达式的使用

            //1.无参无返回值的lambda表达式
            Action action1 = () =>
            {
                Console.WriteLine("无参无返回值的lambda表达式");
            };
            action1();//无参无返回值的lambda表达式

            //2.有参无返回值的lambda表达式
            Action<int> action2 = (int value) =>
            {
                Console.WriteLine("有参无返回值的lambda表达式 int value:{0}", value);
            };
            action2(100);//有参无返回值的lambda表达式 int value:100

            //3.有参无返回值的lambda表达式省略参数类型的写法 参数类型可以省略 参数类型和委托或事件容器一致
            Action<int> action3 = (value) =>
            {
                Console.WriteLine("省略参数类型的写法 value:{0}", value);
            };
            action3(200);//省略参数类型的写法 value:200

            //4.有参有返回值的lambda表达式
            Func<string, int> action4 = (value) =>
            {
                Console.WriteLine("有参有返回值的lambda表达式 value:{0}", value);
                return 1;
            };
            Console.WriteLine(action4("123123"));
            //有参有返回值的lambda表达式 value:123123
            //1

            //其它传参使用等和匿名函数一样
            //缺点也是和匿名函数一样的 添加到委托后没有办法指定移除lambda表达式
            #endregion

            #region 知识点四 闭包

            //调用构造函数 临时变量value的生命周期被改变了
            Test test = new Test();//10

            test.DoEvent();
            //i: 10
            //index: 0
            //i: 10
            //index: 1
            //i: 10
            //index: 2
            //i: 10
            //index: 3
            //i: 10
            //index: 4
            //i: 10
            //index: 5
            //i: 10
            //index: 6
            //i: 10
            //index: 7
            //i: 10
            //index: 8
            //i: 10
            //index: 9
            #endregion
        }
    }

    //class语句块外 namespace语句块内

    #region 知识点四 闭包
    //闭包的基本概念
    //内层的函数可以引用包含在它外层的函数的变量
    //即使外层函数的执行已经终止
    //注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

    //测试类
    class Test
    {
        //事件
        public event Action myEvent;

        public Test()
        {
            //构造函数的临时变量 正常来说构造函数结束他就会被释放
            int value = 10;

            //这里就形成了闭包
            //因为 当构造函数执行完毕时  其中申明的临时变量value的声明周期被改变了
            //value还有作用不会被释放 一直会存到action里面 除非手动把action置空
            myEvent = () =>
            {
                Console.WriteLine(value);
            };

            for (int i = 0; i < 10; i++)
            {
                //注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

                //此index 非彼index 每次for循环的index都是新的临时变量
                int index = i;

                myEvent += () =>
                {
                    Console.WriteLine("i:" + i);
                    //这里的会一直会输出 i:10
                    //本质上传的是i变量 这个i变量在事件没有执行时一直没用到 一直存在事件容器里
                    //但他真正用的时候 这个i的最终值已经是10了 所以只会输出 i:10
                    //i在父函数范围内(Test这个函数的语句块)的最终值是10

                    Console.WriteLine("index:" + index);
                    //这里会逐个输出index:0-9
                    //因为每次for循环的index都是新的临时变量 所以一共会有10个index临时变量
                    //所以保存的index都不同
                    //index在父函数范围内(每一次for循环的语句块)的最终值就是int index = i时的赋值
                };
            }
        }

        //执行事件
        public void DoEvent()
        {
            myEvent();
        }
    }

    #endregion

    //总结
    //匿名函数的特殊写法 就是 lambda表达式
    //固定写法 就是 (参数列表)=>{}
    //参数列表 可以直接省略参数类型
    //主要在 委托传递和存储时  为了方便可以直接使用匿名函数或者lambda表达式
    //缺点:无法指定移除
}

16.3 练习题

有一个函数,会返回一个委托函数,这个委托函数中只有一句打印代码,之后执行返回的委托函数时,可以打印出1~10

class语句块内 主函数外

// 要用到闭包的知识

// lambda表达式
static Action GetFun()
{
    Action action = null;
    for (int i = 1; i <= 10; i++)
    {
        int index = i;
        action += () =>
        {
            Console.WriteLine(index);
        };
    }
    return action;
}

// 匿名函数
static Action GetFun2()
{
    Action action = null;
    for (int i = 1; i <= 10; i++)
    {
        int index = i;
        action += delegate ()
        {
            Console.WriteLine(index);
        };
    }
    return action;
}

// 调用别的函数包裹起来
static Action GetFun3()
{
    Action action = null;
    for (int i = 1; i <= 10; i++)
    {
        int index = i;

        // action += Show;
        // 报错 首先action是无参无返回值 Show方法有参无返回值 不匹配
        // 其次 计算action能够匹配Show 也无法传入参数 因为只是传函数名

        action += () =>
        {
            Show(index);
        };
    }
    return action;
}

// 打印函数
static void Show(int i)
{
    Console.WriteLine(i);
}

主函数内

GetFun()();
GetFun2()();
GetFun3()();
// 都能正常打印出1~10
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10

16.4 练习题代码

using System;

namespace Lesson15_练习题
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("lambda表达式 练习题");

            //主函数内

            #region 练习题一
            GetFun()();
            GetFun2()();
            GetFun3()();
            //都能正常打印出1~10
            //1
            //2
            //3
            //4
            //5
            //6
            //7
            //8
            //9
            //10
            #endregion
        }

        //class语句块内 主函数外

        #region 练习题一
        //有一个函数,会返回一个委托函数,这个委托函数中只有一句打印代码
        //之后执行返回的委托函数时,可以打印出1~10

        //要用到闭包的知识

        //lambda表达式
        static Action GetFun()
        {
            Action action = null;
            for (int i = 1; i <= 10; i++)
            {
                int index = i;
                action += () =>
                {
                    Console.WriteLine(index);
                };
            }
            return action;
        }

        //匿名函数
        static Action GetFun2()
        {
            Action action = null;
            for (int i = 1; i <= 10; i++)
            {
                int index = i;
                action += delegate ()
                {
                    Console.WriteLine(index);
                };
            }
            return action;
        }

        //调用别的函数包裹起来
        static Action GetFun3()
        {
            Action action = null;
            for (int i = 1; i <= 10; i++)
            {
                int index = i;

                //action += Show;
                //报错 首先action是无参无返回值 Show方法有参无返回值 不匹配
                //其次 计算action能够匹配Show 也无法传入参数 因为只是传函数名

                action += () =>
                {
                    Show(index);
                };
            }
            return action;
        }
        //打印函数
        static void Show(int i)
        {
            Console.WriteLine(i);
        }

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏