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

news2025/3/14 0:27:41

文章目录

  • 一、匿名函数(`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.coloradmin.cn/o/2267908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

脱离电路图编程

SM0.0常开始终吸合 SM0.1&#xff08;特殊中继&#xff09; 常开&#xff1a;闭合一次再断开 常闭&#xff1a;断开一次再闭合 上述是依据电路图编程

人工智能及深度学习的一些题目

1、一个含有2个隐藏层的多层感知机&#xff08;MLP&#xff09;&#xff0c;神经元个数都为20&#xff0c;输入和输出节点分别由8和5个节点&#xff0c;这个网络有多少权重值&#xff1f; 答&#xff1a;在MLP中&#xff0c;权重是连接神经元的参数&#xff0c;每个连接都有一…

OpenGL变换矩阵和输入控制

在前面的文章当中我们已经成功播放了动画&#xff0c;让我们的角色动了起来&#xff0c;这一切变得比较有意思了起来。不过我们发现&#xff0c;角色虽然说是动了起来&#xff0c;不过只是在不停地原地踏步而已&#xff0c;而且我们也没有办法通过键盘来控制这个角色来进行移动…

【ArcGIS Pro】完整的nc文件整理表格模型构建流程及工具练习数据分享

学术科研啥的要用到很多数据&#xff0c;nc文件融合了时间空间数据是科研重要文件。之前分享过怎样将nc文件处理成栅格后整理成表格。小编的读者还是有跑不通整个流程的&#xff0c;再来做一篇总结篇&#xff0c;也分享下练习数据跟工具&#xff0c;如果还是弄不了的&#xff0…

linux-21 目录管理(一)mkdir命令,创建空目录

对linux而言&#xff0c;对一个系统管理来讲&#xff0c;最关键的还是文件管理。那所以我们接下来就来看看如何实现文件管理。当然&#xff0c;在文件管理之前&#xff0c;我们说过&#xff0c;文件通常都放在目录下&#xff0c;对吧&#xff1f;所以先了解目录&#xff0c;可能…

vulnhub jangow靶机

1.扫描靶机IP arp-scan -l如果扫不到靶机的话根据以下配置 启动时点击第二个 按回车 继续选择第二个 按e进入编辑 删除"recovery nomodeset" 在末尾添加"quiet splash rw init/bin/bash" Ctrlx 启动进入如下界面 passwd修改root密码 重启电脑登录root修…

惠普HP proliant DL380 G6服务器使用

惠普HP proliant DL380 G6服务器使用经历 前言 HP ProLiant DL380 G6是一款机架式服务器&#xff0c;标配1个Xeon E5504处理器。 已被列入“高耗能老旧通信设备淘汰指导目录” 配置 基本类别 类别 机架式 结构 2U 内存 内存类型 DDRIII 内存大小 4GB&#xff08;单条插槽…

java 斐波那契查找,涵盖原理、算法分析、实现细节、优缺点、应用场景等

一、定义 斐波那契查找&#xff08;Fibonacci Search&#xff09;是一种基于斐波那契数列的查找算法&#xff0c;适用于已排序的数组。它利用斐波那契数列的性质来减少比较次数&#xff0c;并且能够在某些条件下比二分查找更快。 更多优质资源推荐&#xff1a; http://sj.yso…

内网学习:工作组用户与权限

目录 一、本地用户组介绍本地工作组介绍用户与组的关系 二、四种用户类型及权限比较本地系统最高权限&#xff08;System账户&#xff09;特性Administrator与System账户的区别 本地最高管理员&#xff08;Administrator用户&#xff09;特性 本地普通管理员特性 本地普通用户特…

图片转成oled使用的字模数据

目录 oled尺寸 如何生成用到的图片 图片转字模 1.首先用Img2Lcd转成bmp单色图片 2.然后用PCtoLCD2002把单色图片转字模 oled尺寸 我使用0.96寸oled模块&#xff0c;对应着的分辨率是128*64&#xff0c;对应着宽高像素比128*64。所以不是随意一张图片就能用的&#xff0c;…

PTPVT 插值说明

文章目录 PTPVT 插值说明 PTPVT 插值说明PVT Hermite插值PVT 三次多项式插值PT 插值Sin轨迹测试结果PVT Hermite插值结果PVT 三次多项式插值结果PT 插值结果 用户轨迹测试结果PVT Hermite插值结果PT 插值结果 PTPVT 插值说明 PT模式&#xff1a; 位置-时间路径插值算法。 PVT模…

Mac安装多个版本node、java、python 等开发软件环境,安装、卸载、升级多个数据库

安装多个版本node、java、python 等开发软件环境 使用nvm&#xff08;Node.js Version Manager&#xff09;来管理多个Node.js版本。 使用jenv来管理多个Java版本。 使用pyenv来管理多个Python版本。 以下是安装和使用这些版本管理器的基本步骤&#xff1a; 1. 安装多个版本…

【深度学习】卷积网络代码实战ResNet

ResNet (Residual Network) 是由微软研究院的何凯明等人在2015年提出的一种深度卷积神经网络结构。ResNet的设计目标是解决深层网络训练中的梯度消失和梯度爆炸问题&#xff0c;进一步提高网络的表现。下面是一个ResNet模型实现&#xff0c;使用PyTorch框架来展示如何实现基本的…

添砖java第四更@(+)@

今天的学习内容主要是围绕着实体类来进行的&#xff0c;就是说在java里面我们常常会把数据存放和方法分别存放在不同的类里面。 首先就是关于实体类是什么&#xff0c;实体类就是只提供了get方法,set方法,和默认构造器的类。 接着就是熟悉java与别的语言的不同之处就在于它是…

算法题(19):多数元素

审题&#xff1a; 数组不为空且一定存在众数。需要返回众数的数值 思路&#xff1a; 方法一&#xff1a;哈希映射 先用哈希映射去存储对应数据出现的次数&#xff0c;然后遍历找到众数并输出 当然也可以在第一次映射的过程中就维护一个出现次数最多的数据&#xff0c;这样子就可…

电子商务网站的三层架构的理解和实践

在电子商务领域&#xff0c;网站架构的设计对于系统的稳定性、可扩展性和用户体验至关重要。其中&#xff0c;三层架构作为一种经典的设计模式&#xff0c;被广泛应用于各类电子商务网站中。本文将从理论、理解和实践三个方面&#xff0c;详细探讨电子商务网站的三层架构。 一、…

LVS 负载均衡原理 | 配置示例

注&#xff1a;本文为 “ LVS 负载均衡原理 | 配置” 相关文章合辑。 部分内容已过时&#xff0c;可以看看原理实现。 使用 LVS 实现负载均衡原理及安装配置详解 posted on 2017-02-12 14:35 肖邦 linux 负载均衡集群是 load balance 集群的简写&#xff0c;翻译成中文就是负…

JavaScript甘特图 dhtmlx-gantt

背景 需求是在后台中&#xff0c;需要用甘特图去展示管理任务相关视图&#xff0c;并且不用依赖vue&#xff0c;兼容JavaScript原生开发。最终使用dhtmlx-gantt&#xff0c;一个半开源的库&#xff0c;基础功能免费&#xff0c;更多功能付费。 甘特图需求如图&#xff1a; 调…

领域驱动设计第一篇-DP主题

一&#xff1a;领域驱动设计概述 领域驱动设计。Domain-Driven Design 可以理解为基于领域的工程设计。 1&#xff1a;什么是领域&#xff1f; 初步理解领域&#xff1a;业务问题的范畴。 领域可大可小&#xff0c;对应着大小业务问题的边界。业务上要做的几个事&#xff0…

EMNLP'24 最佳论文解读 | 大语言模型的预训练数据检测:基于散度的校准方法

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 点击 阅读原文 观看作者讲解回放&#xff01; 作者简介 张伟超&#xff0c;中国科学院计算所网络数据科学与技术重点实验室三年级直博生 内容简介 近年来&#xff0c;大语言模型&#xff08;LLMs&#xff09;的…