当前位置: 首页 > article >正文

【从零开始入门unity游戏开发之——C#篇34】C#匿名函数(delegate )和Lambda表达式

文章目录

  • 一、匿名函数(`delegate` )
    • 1、什么是匿名函数?
    • 2、匿名函数的基本语法
      • 2.1 语法
      • 2.2 **没有参数的匿名函数:**
      • 2.3 **有参数的匿名函数:**
      • 2.4 **有返回值的匿名函数:**
    • 3、匿名函数的使用示例
      • 3.1 作为参数传递匿名函数
      • 3.2 作为返回值返回匿名函数
    • 4、匿名函数的缺点
      • 4.1 无法单独移除
      • 4.2 匿名函数难以调试
      • 4.3 性能开销
    • 5. 匿名函数与闭包
    • 6、匿名函数的总结
  • 二、Lambda表达式
    • 1 、什么是 Lambda 表达式
    • 2、Lambda 表达式的语法
    • 3、匿名函数转Lambda 表达式示例
    • 4、Lambda 表达式的使用
      • 4.1 **无参无返回值**:
      • 4.2 **有参的无返回值**:
      • 4.3 **有返回值**:
    • 5、闭包
      • 5.1 概念
      • 5.2 示例
      • 5.3 如何避免这种问题?
    • 5、总结
    • 6、注意:
  • 三、委托和匿名函数的区别
  • 专栏推荐
  • 完结

一、匿名函数(delegate

1、什么是匿名函数?

匿名函数是没有名字的函数。它通常用于在需要传递函数或行为时,避免了定义一个额外的命名函数。匿名函数最常用的场景是在委托(delegate)和事件(event)的处理过程中。

常见使用场景:

  • 委托传递时
  • 事件绑定时

2、匿名函数的基本语法

2.1 语法

匿名函数使用delegate关键字来声明,语法如下:

delegate (参数列表) { 函数逻辑 };

2.2 没有参数的匿名函数:

Action a = delegate {
    Console.WriteLine("匿名函数逻辑");
};
a();

2.3 有参数的匿名函数:

Action<int, string> b = delegate (int a, string b) {
    Console.WriteLine(a);
    Console.WriteLine(b);
};
b(108, "123");

2.4 有返回值的匿名函数:

Func<string> c = delegate {
    return "返回的字符串";
};
Console.WriteLine(c());

3、匿名函数的使用示例

3.1 作为参数传递匿名函数

可以将匿名函数作为参数传递给方法:

public void DoSomething(int a, Action fun)
{
    Console.WriteLine(a);
    fun(); // 调用传入的匿名函数
}

// 调用方法时传递匿名函数
DoSomething(10, delegate {
    Console.WriteLine("传入的匿名函数逻辑");
});

3.2 作为返回值返回匿名函数

匿名函数可以作为方法的返回值:

public Action GetFun()
{
    return delegate {
        Console.WriteLine("匿名函数返回值");
    };
}

Action action = GetFun();
action();

直接调用返回的委托函数

GetFun()();  // 直接调用返回的匿名函数

4、匿名函数的缺点

4.1 无法单独移除

匿名函数没有名称,因此无法通过直接引用来移除它们。尤其在委托或事件中,当添加多个匿名函数时,无法移除某一个匿名函数。

例如:

Action ac = delegate {
    Console.WriteLine("匿名函数一");
};

ac += delegate {
    Console.WriteLine("匿名函数二");
};

ac();  // 会打印 "匿名函数一" 和 "匿名函数二"

ac -= delegate {
    Console.WriteLine("匿名函数二");
};  // 编译错误:不能移除匿名函数

ac();  // 依然打印 "匿名函数一" 和 "匿名函数二"

由于匿名函数没有名称,无法通过-=运算符去移除某一个特定的匿名函数。

4.2 匿名函数难以调试

因为匿名函数没有名称,所以它们难以调试。无法直接断点到匿名函数内,也无法追踪其调用栈。

4.3 性能开销

在某些情况下,匿名函数会增加额外的闭包和委托创建的性能开销,尤其是当它们在频繁调用的场景中使用时。

5. 匿名函数与闭包

匿名函数在使用时可以捕获外部变量(即使外部变量在匿名函数外部的作用域中已经超出了生命周期),这被称为闭包

例如:

int x = 10;
Action action = delegate {
    Console.WriteLine(x);  // 捕获了外部变量x
};
x = 20;
action();  // 打印 20

这个例子中,匿名函数捕获了外部变量x,并且打印了x的当前值。由于匿名函数形成闭包,它保存了对x的引用,即使x的值被修改,匿名函数依然会使用最新的值。

6、匿名函数的总结

  • 定义简洁:匿名函数没有名称,可以直接在需要的地方声明并使用,适用于委托和事件的传递。
  • 用途广泛:尤其在回调函数、事件处理、LINQ表达式等场景中非常常见。
  • 缺点:无法移除、调试困难、性能开销较大等。
  • 闭包特性:匿名函数可以捕获外部变量(闭包),这使得它们能够访问外部作用域的变量。

二、Lambda表达式

1 、什么是 Lambda 表达式

Lambda 表达式可以被看作是匿名函数的简写。它与匿名函数的区别在于,写法更为简洁,且与委托或者事件配合使用的方式相同。

  • 匿名函数:没有名称的函数,通常用 delegate 关键字定义。
  • Lambda 表达式:更简洁的函数表示法,使用 => 符号。

2、Lambda 表达式的语法

Lambda 表达式的语法格式如下:

(parameters) => expression_or_statement_block
  • 参数列表:Lambda 表达式的输入参数,可以省略类型,C# 会根据上下文推断类型。
  • 表达式或语句块:Lambda 表达式的执行代码。如果是单个表达式,返回值会被自动推断。如果是多行代码,必须用 {} 包裹。

3、匿名函数转Lambda 表达式示例

  • 匿名方法(使用 delegate):

    delegate void MyDelegate(int x);
    MyDelegate d = delegate(int x) { Console.WriteLine(x); };
    d(10);
    
  • Lambda 表达式

    Action<int> d = (int x) => { Console.WriteLine(x); };
    d(10);
    

4、Lambda 表达式的使用

4.1 无参无返回值

Action a = () => Console.WriteLine("无参无返回值的 Lambda 表达式");
a();

4.2 有参的无返回值

Action<int> a2 = (int value) => Console.WriteLine($"有参数的 Lambda 表达式 {value}");
a2(100);

可以省略参数类型

Lambda 表达式的参数类型可以根据委托类型自动推断,所以可以省略参数类型。

Action<int> a3 = (value) => Console.WriteLine($"省略参数类型的写法 {value}");
a3(200);

4.3 有返回值

使用 Func 委托时,Lambda 表达式可以有返回值。

Func<string, int> a4 = (value) =>
{
    Console.WriteLine($"有返回值的 Lambda 表达式 {value}");
    return 1;
};
Console.WriteLine(a4("123123"));

5、闭包

5.1 概念

  • 闭包 是 Lambda 表达式捕获外部作用域中的变量的机制。
  • Lambda 表达式不仅仅捕获变量的值,而是捕获了变量的引用。它可以在 Lambda 表达式的执行时继续访问这些变量,即使这些变量所在的作用域已经结束。

5.2 示例

闭包常常会在循环中表现出意外的行为,因为 Lambda 表达式捕获了循环变量的引用,而不是它的值。以下是一个例子:

public class ClosureInLoop
{
    public void Test()
    {
        List<Action> actions = new List<Action>();

        for (int i = 0; i < 5; i++)
        {
            // Lambda 表达式捕获了循环变量 i 的引用
            actions.Add(() => Console.WriteLine(i));
        }

        // 输出的结果是 5 次 5,因为 Lambda 捕获的是 i 的引用,而不是它的值
        foreach (var action in actions)
        {
            action();
        }
    }
}

解释:

在上面的代码中,我们将 Lambda 表达式添加到一个列表中。每个 Lambda 表达式都捕获了循环变量 i 的引用。因为 Lambda 表达式没有立即执行,而是在循环结束后才被执行,所以它们访问的是最终的 i 值(即 5)。所有的 Lambda 表达式都会输出 5,而不是它们在创建时的 i 值。

5.3 如何避免这种问题?

如果你希望 Lambda 表达式捕获的是 i 在每次迭代时的值,而不是循环结束后的值,你可以在 Lambda 表达式中引入一个局部变量来保存当前的 i 值:

public class ClosureInLoopFix
{
    public void Test()
    {
        List<Action> actions = new List<Action>();

        for (int i = 0; i < 5; i++)
        {
            int capturedValue = i;  // 将 i 的值保存到一个局部变量
            actions.Add(() => Console.WriteLine(capturedValue));
        }

        foreach (var action in actions)
        {
            action();  // 现在会输出 0, 1, 2, 3, 4
        }
    }
}

在这种情况下,capturedValue 是在每次循环时创建的一个新的局部变量,这样每个 Lambda 表达式都会捕获不同的 capturedValue,而不是 i 的引用。

5、总结

  • Lambda 表达式 是匿名函数的简化写法,通常用于委托或事件处理。
  • 语法(参数列表) => 表达式/语句块
  • 闭包:Lambda 表达式可以捕获外部函数的局部变量,并且可以在外部函数已经结束后依然访问这些变量。

6、注意:

  • Lambda 表达式使代码更简洁,但缺点是无法明确地移除或管理。
  • 闭包 可能导致意外的引用或性能问题,特别是在循环或多次调用 Lambda 时。

三、委托和匿名函数的区别

C# 中的匿名函数和委托确实有一些相似之处,尤其是在代码外观上可能会让人觉得它们很像,但它们并不是同一个概念。

  • 委托 是一种类型,它定义了方法的签名,并可以引用任何符合该签名的方法。
  • 匿名函数 是没有显式名称的函数,它们可以在声明时立即定义行为,包括匿名方法和 Lambda 表达式。
  • 委托和匿名函数一起使用:匿名函数可以被赋值给委托类型的变量,从而实现更灵活和简洁的编程风格。
  • delegate 关键字 主要用于定义委托类型,但在匿名方法中也会出现,用于直接定义函数体。Lambda 表达式则不需要显式的 delegate 关键字。

专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.kler.cn/a/457640.html

相关文章:

  • Niushop商城商业插件_cps联盟_包装转换_视频购物_同城配送_上门预约等插件的安装方法
  • CentOS — 目录管理
  • 学技术学英文:Tomcat的线程模型调优
  • 酒店管理系统|Java|SSM|VUE| 前后端分离
  • Git的使用流程(详细教程)
  • Mac 版本向日葵退出登录账号
  • 【探花交友】通用设置总结笔记
  • Spring Boot Actuator、Spring Boot Actuator使用、Spring Boot Actuator 监控、Spring程序监控
  • libreoffice在Windows和Linux环境的安装和结合Springboot使用教程
  • Windows安装Confluence详解
  • YOLOv10-1.1部分代码阅读笔记-conv.py
  • React(二)——注册页/登录页/Reducer/
  • Linux实验报告6-用户管理
  • Metagenome宏基因组,未识别的物种unclassified
  • 【Unity3D】ECS入门学习(五)共享组件 ISharedComponentData
  • MySQL三层B+树能存多少条数据
  • 鸿蒙项目云捐助第三十一讲云捐助项目云前台显示商品列表
  • UDP协议解说
  • RJ45网口模块设计
  • 常见网络攻击场景常被用于测试系统安全性
  • Android中使用AIDL实现进程通信
  • ArrayList和LinkedList的区别、优缺点与使用场景
  • 生产力利器,Mac 系统优选,keychron K10Max 三模键盘体验分享
  • QT-------认识QT
  • 呼叫中心中间件免费体验测试和freeswitch部署方案
  • Linux CPU调度算法