(六)CSharp-CSharp图解教程版-委托

news2024/11/23 8:02:34

一、委托概述

1、什么是委托

委托和类一样,是一种用户定义类型(即是一种类,所以也是一个引用类型)。在它们组成的结构方面区别是,类表示的是数据和方法的集合,而委托则持有一个或多个方法

请添加图片描述

可以把 delegate 看作是一个包含有序方法列表的对象,这些方法具有相同的签名和返回类型。

  • (1)方法的列表称为调用列表。

  • (2)委托持有的方法可以来自任何类或结构,只要它们在下面两方法匹配:

    • 委托的返回类型;
    • 委托的签名(包括 ref 和 out 修饰符)
  • (3)调用列表中的方法可以是实例方法也可以是静态方法

  • (4)在调用委托的时候,会执行其调用列表中的所有方法。

请添加图片描述

2、声明委托类型

声明委托代码:

delegate void MyDel(int x);
//delegate:关键字
//void:返回类型
//MyDel:委托类型名称
//MyDel(int x):签名

委托与方法在声明时有两点不同:

  • 以 delegate 关键字开头;
  • 没有方法主体。

说明: 虽然委托类型声明看上去和方法的声明一样,但它不需要在类内部声明,因为它是类型声明。

请添加图片描述

3、创建委托对象

委托类型的变量声明:

MyDel delVar;
//MyDel:委托类型
//delVar:变量

有两种创建委托对象的方法:

(1)使用带 new 运算符的对象创建表达式。
new 运算符的操作数的组成:

  • 委托类型名。
  • 一组圆括号,其中包含作为调用列表中第一个成员的方法的名称,该方法可以是实例方法或静态方法。

假设 myInstObj 是类对象,MyM1 是 myInstObj 的一个实例方法。 SCLass 是类,OtherM2 是 SCLass 的静态方法。

//创建委托并保存引用
delVar = new MyDel(myInstObj.MyM1);
delVar = new MyDel(SCLass.OtherM2);

(2)快捷语法
仅由方法说明符构造。这种快捷语法能够工作是因为在方法名称和其相应的委托类型之间存在隐式转换。

//创建委托并保存引用
delVar = myInstObj.MyM1;
delVar = SClass.OhterM2;

以下是创建委托对象的完整代码:

delegate void MyDel(int x);

//使用 new 运算符方式
MyDel delVar,dVar;
delVar = new MyDel(myInstObj.MyM1);
dVar = new MyDel(SClass.OtherM2);

//快捷语法方式
MyDel delVar = myInstObj.MyM1;
MyDel dVar = SClass.OhterM2;

除了为委托分配内存,创建委托对象还会把第一个方法放入委托的调用列表。

请添加图片描述

4、给委托赋值

由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的引用。旧的委托对象被垃圾回收器回收。

MyDel delVar;
delVar = myInstObj.MyM1;
delVar = SClass.OhterM2;

请添加图片描述

二、组合委托

1、什么是组合委托

委托也可以包含多个方法。

也称为多播委托。

比如,创建了3个委托。其中第3个委托由前两个委托组合而成。

MyDel delA = myInstObj.MyM1;
MyDel delB = SClass.OhterM2;

MyDel delC= delA + delB;//组合调用列表

委托是恒定的,不会因为组合而改变原委托。委托对象被创建后不能再被改变。

请添加图片描述

2、为委托添加方法

使用 += 运算符来添加方法。

MyDel delVar = inst.MyM1;
//增加两个方法
delVar += SCl.m3;
delVar += X.Act;

在使用 += 运算符时,实际发生的是创建了一个新的委托,其调用列表是左边的委托加上右边方法的组合。然后将这个新的委托赋值给 delVar。
我有疑问: 委托是可以存放多个方法的方法列表,那为什么还要先创建一个新的委托,再赋值给 delVar 委托对象呢?这里的赋值是不是指在原有的基础上再增加一个元素(方法)的意思?比如类似于string str = “a”; str += “bc”; 【temp = str+“bc”; 把 temp 赋值到 str 当中】 )

2、从委托移除方法

使用 -= 运算符从委托移除方法。(在这里我似乎理解了事件为什么还能跟委托有一定的关联,此处写法很像是事件的订阅和取消订阅的工作方式)

//从委托移除方法
delVar -= SCl.m3;

与为委托添加方法一样,其实是创建了一个新的委托。新的委托是旧委托的副本——只是没有了已经被移除方法的引用。(加强理解: 比如 string str = “abc”; 去除了"bc"后得出一个新值 temp = “a”,然后再把 temp 赋值给 str。)

移除委托时需要记住的一些事项

  • 如果在调用列表中的方法有多个实例, -= 运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例。
  • 试图删除委托中不存在的方法将无效。
  • 试图调用空委托会抛出异常。可以通过将委托和 null 进行比较来判断委托的调用列表是否为空。如果调用列表为空,则委托是 null。

3、调用委托

调用委托需要知道的重要事项:

  • 可以通过两种方法调用委托。一种是像调用方法一样调用委托,另一种是使用委托的 Invoke 方法
  • 在圆括号里传参。即 ==委托对象 (参数) ==或者 Invke (参数)
  • 如果一个方法在调用列表中多次出现,则在调用委托时,每次在列表中遇到该方法时都会调用它。

代码例子:

 class TestClass
    {
        static public void Fun(string str)
        {
            Console.WriteLine(str);
        }
    }
    class Program
    {
        delegate void MyDel(string str);
        static void Main(string[] args)
        {
            MyDel delVar = TestClass.Fun;
            //又增加了该方法,于是调用列表出现两次该方法
            delVar += TestClass.Fun;

            delVar.Invoke("调用Fun");

            delVar-= TestClass.Fun;

            //移除一次后,还剩一个 TestClass.Fun
            delVar.Invoke("移除一次该方法后,调用Fun");

            Console.ReadKey();
        }
    }

输出结果:

调用Fun
调用Fun
移除一次该方法后,调用Fun

  • 调用时委托不能为空(null),否则将引发异常。(不过可以使用 if 语句进行检查 也可以使用 delVal.?Invoke(参数) ,通过?空条件运算符来检查)

4、委托的示例

本书的示例:

  delegate void PrintFunction();
  class Test
    {
        public void Print1() {  Console.WriteLine("Print1 -- instance");   }
        public static void Print2() { Console.WriteLine("Print2 -- static"); }
    }

    class Program
    {
        
        static void Main(string[] args)
        {
            Test t = new Test();
            PrintFunction pf;

            pf = t.Print1;

            //给委托增加3个另外的方法
            pf += Test.Print2;
            pf += t.Print1;
            pf += Test.Print2;
            //现在,委托含有4个方法

            if (null != pf)
                pf();
            else
                Console.WriteLine("Delegate is empty.");

            Console.ReadKey();
        }
    }

输出结果:

Print1 – instance
Print2 – static
Print1 – instance
Print2 – static

5、调用带返回值的委托

如果委托有返回值并且在调用列表中有一个以上的方法,会发生如下的情况:

  • 调用列表中最后一个方法返回的值就是委托调用返回的值
  • 调用列表中所有其他方法的返回值都会被忽略。
 class MyClass
    {
        int IntValue = 5;
        public int Add2() { IntValue += 2; return IntValue; }
        public int Add3() { IntValue += 3; return IntValue; }
        //为了加强理解书中的内容,自己添加的方法Add4
        public int Add4() {  return 1234; }
    }

    class Program
    {
        
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();
            MyDel mDel = mc.Add2;//返回值为7被忽略

            //为了验证输出结果是否为最后一个方法返回的结果
            //mDel += mc.Add4;

            mDel += mc.Add3;//返回值为10被忽略
            mDel += mc.Add2; 
            //mDel += mc.Add4;//若执行该语句,则输出内容为1234
            Console.WriteLine($"Value:{ mDel() }");

            Console.ReadKey();
        }
    }

输出结果:

Value:12

6、调用带引用参数的委托

如果委托有引用参数,参数值会根据调用列表中的一个或多个方法的返回值而改变。

  class MyClass
    {
        public void Add2(ref int x) { x += 2; }
        public void Add3(ref int x) { x += 3; }
    }

    class Program
    {
        
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();
            MyDel mDel = mc.Add2;

            mDel += mc.Add3;
            mDel += mc.Add2;

            int x = 5;
            mDel(ref x);

            Console.WriteLine($"Value:{ x }");

            Console.ReadKey();
        }
    }

输出结果:

Value:12

三、匿名方法

1、什么是匿名方法

匿名方法是在实例化委托时内联声明的方法。

使用条件: 如果方法只会被使用一次——用来实例化委托,则使用匿名方法来处理。

在如下地方使用匿名方法:

  • 声明委托变量时作为初始化表达式。
  • 组合委托时在赋值语句的右边。
  • 为委托增加事件时在赋值语句的右边。

匿名方法的语法

匿名方法表达式的语法包含如下组成部分:

  • delegate 类型关键字。
  • 参数列表,如果语句块没有使用任何参数则可以省略。
  • 语句块,它包含了匿名方法的代码。
delegate (Parameters){ ImplementationCode }
//delegate:关键字
//Parameters:参数列表
//ImplementationCode:语句块

1)返回类型

匿名方法不会显式声明返回值。

delegate int OhterDel(int InParam);

static void Main()
{

OtherDel del = delegate(int x)
{
retrun x + 20;
};

}

2)参数

除了数组参数,匿名方法的参数列表必须在如下3方面与委托匹配

  • 参数数量;
  • 参数类型及位置;
  • 修饰符。

可以通过使圆括号为空省略圆括号来简化匿名方法的参数列表,但必须满足以下两个条件:

  • 委托的参数列表不包含任何 out 参数;
  • 匿名方法不使用任何参数。
delegate void SomeDel(int x);
SomeDel SDel = delegate
            {
                PrintMessage();
                Cleanup();
            };

不懂:如果不使用参数的话,怎么对参数进行操作呢?这不就是相当于无参数的委托么?

3)params 参数

如果委托声明的参数列表包含了 params 参数,那么匿名方法的参数列表将忽略 params 关键字。

  • 委托类型声明指定最后一个参数为 params 类型的参数;
  • 匿名方法参数列表必须省略 params 关键字。
delegate void SomeDel(int X,params int[] Y);

SomeDel mDel = delegate(int X,int Y)
{

};

2、变量和参数的作用域

  delegate void MyDel(int x);

    class Program
    {
        
        static void Main(string[] args)
        {
            MyDel mDel = delegate (int y)
            {
                int z = 10;
                Console.WriteLine("{0},(1)", y, z);
                //y,z 的作用域在花括号内
            };

            Console.WriteLine("{0},(1)", y, z);//编译错误
            Console.ReadKey();
        }
    }

外部变量

  • 外围作用域的变量叫作外部变量。(在 delegate 花括号之外的变量)
  • 用在匿名方法实现代码中的外部变量称为被方法捕获。(外部变量在 delegate 花括号内使用。其实外部方法也作用在匿名方法内?书上没提到这点,不知道这算不算被方法捕获。)

四、Lambda 表达式

Lambda 表达式简化了匿名方法的语句。

把匿名方法转换为 Lambda 表达式:

  • 删除 delegate 关键字
  • 在参数列表和匿名方法主体之间放置 Lambda 运算符 =>。Lambda 运算符读作“goes to”。
MyDel del = delegate(int x){ return x + 1; }; //匿名方法
MyDel le1 = (int x) => { return x + 1; }; //Lambda 表达式

进一步简化 Lambda 表达式:

  • 委托参数

    • 1)带有类型的参数列表称为显式类型。
    • 2)省略类型的参数列表称为隐式类型。
  • 如果只有一个隐式类型参数,可以省略两端的圆括号。

  • Lambda 表达式的主体是语句块或表达式,如果语句块包含了一个返回值语句,可以将语句块替换为 return 关键字后的表达式。(即 { return x + 1 ; }; 替换为 x + 1 ; )

//匿名方法
MyDel del = delegate(int x){ return x + 1; } ;
// Lambda 表达式
MyDel le1 =         (int x) => { return x + 1; } ;
MyDel le2 =             (x) => { return x + 1; } ;
MyDel le3 =               x => { return x + 1; } ;
MyDel le4 =               x =>  x + 1 ; 

有关 Lambda 表达式的参数列表的要点:

  • Lambda 表达式参数列表中的参数必须在参数数量、类型和位置上与委托相匹配
  • 表达式的参数列表中的参数不一定需要包含类型(隐式类型),除非委托有 ref 或 out 参数——此时必须注明类型(显式类型)。
  • 如果只有一个参数,并且是隐式类型的,则两端的圆括号可以省略,否则必须有括号。
  • 如果没有参数,必须使用一组空的圆括号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/628505.html

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

相关文章

Spring架构篇--2.7.1 远程通信基础--Netty原理--NioEventLoopGroup

前言:在使用Netty 时不管是服务端还是客户端都需要 new NioEventLoopGroup 对象进行工作,NioEventLoopGroup的作用是什么呢; 1 NioEventLoopGroup 类图: 从类名字来看它是一个Nio 流的事件轮询器组,既然是一组顾名思…

使用dataFEED OPC Suite将西门子PLC数据转发至阿里云RDS数据库

一 背景 工业现场级别的各种设备会产生大量的数据,这些数据包含生产过程的各种信息,在经过数据库等IT应用的处理后,可为企业提供全面的生产数据分析和决策支持。以往工厂的数据库通常部署在本地,然而得益于云计算的快速发展以及云…

k8s harbor镜像仓库搭建

1.前言 Harbor 是一个开源的云原生镜像仓库,用于存储和分发 Docker 镜像。它提供了一些安全性和管理方面的功能,使得用户可以更好地管理和共享 Docker 镜像 2.配置harbor搭建环境 harbor的搭建需要用到docker、docker-compose服务 docker搭建参考&am…

Vivado 下 IP核之双端口 RAM 读写

目录 Vivado 下 IP核之双端口 RAM 读写 1、RAM IP 核简介 2、实验任务 3、程序设计 3.1、RAM IP 核配置 3.2、顶层模块设计 (1)绘制波形图 4、编写代码 4.1、顶层模块 ip_2port_ram 4.2、RAM 写模块设计 4.3、ram_wr 模块代码 4.4、RAM 读模…

基于graalvm和java swing制作一个文件差异对比的原生应用,附源码

文章目录 1、DFDiff介绍2、软件架构3、安装教程3.1、编译为jar包运行3.2、编译为原生应用运行 4、运行效果图5、项目源码地址 1、DFDiff介绍 当前已实现的功能比较两个文件夹内的文件差异,已支持文件差异对比。 2、软件架构 软件架构说明 开发环境是在OpenJDK17&…

安装 Kafka

文章目录 1.选择操作系统2.配置 Java 环境3.安装 ZooKeeper4.安装 broker(1)安装 broker(2)验证是否安装正确 5.配置 broker(1)常规配置(2)主题的默认配置 6.配置 Kafka 集群&#x…

CAC2.0全新升级发布,为企业邮箱筑起安全壁垒!

5月31日,Coremail举办了【聚焦盗号,企业邮件安全的威胁分析与应对】直播交流会。直播会上Coremail邮件安全团队就邮箱盗号问题进行了深度分享。 面对如此肆虐的盗号现象和即将到来的攻击暴破高峰期,各行业应该如何应对防护邮箱安全呢&#xf…

什么是防火墙?它有什么作用?

作者:Insist-- 个人主页:insist--个人主页 作者会持续更新网络知识和python基础知识,期待你的关注 目录 一、什么是防火墙 二、防火墙的分类 1、软件防火墙 2、硬件防火墙 三、防火墙的作用 1、防止病毒 2、防止访问不安全内容 3、阻…

如何使用AI帮你制作PPT

一:前言 ChatGPT:智能AI助你畅聊天地 在现代人日益忙碌的生活中,难免需要一些轻松愉快的聊天来放松身心。而现在,有了 ChatGPT,轻松愉快的聊天变得更加智能、有趣且不受时间、地点限制! 什么是 ChatGPT&…

嵌入式Linux系统中SPI 子系统基本实现

1、SPI 驱动源文件目录 Linux common spi driver kernel-4.14/drivers/spi/spi.c Linux 提供的通用接口封装层驱动 kernel-4.14/drivers/spi/spidev.c linux 提供的 SPI 通用设备驱动程序 kernel-4.14/include/linux/spi/spi.h linux 提供的包含 SPI 的主要数据结构和函数…

sourcetree的使用

目录 前言 一、Sourcetree简介 二、创建分支与合并分支 三、合并冲突问题 总结 前言 今天提交项目代码时,接触到非常好用方便的可视化Git管理提交软件Sourcetree,今天记录一下使用过程 一、Sourcetree简介 通过Git可以进行对项目的版本管理,但是如果直接使用Git的软件会比…

MinIO快速入门——在Linux系统上安装和启动

1、简介 MinIO 是一款基于Go语言发开的高性能、分布式的对象存储系统。客户端支持Java,Net,Python,Javacript, Golang语言。MinIO系统,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。 2、环境搭建&#…

【MySQL】一文带你了解数据库约束

文章目录 1. 约束类型2.PRIMARY KEY:主键约束3.FOREIGN KEY:外键约束4.NOT NULL:非空约束5.UNIQUE:唯一约束5.DEFAULT:默认值约束6.总结 1. 约束类…

用数据说话,R语言有哪七种可视化应用?

今天,随着数据量的不断增加,数据可视化成为将数字变成可用的信息的一个重要方式。R语言提供了一系列的已有函数和可调用的库,通过建立可视化的方式进行数据的呈现。在使用技术的方式实现可视化之前,我们可以先和AI科技评论一起看看…

SpringBoot 源码分析准备应用上下文(2)-prepareContext

一、入口 /*** Run the Spring application, creating and refreshing a new* {link ApplicationContext}.* param args the application arguments (usually passed from a Java main method)* return a running {link ApplicationContext}*/public ConfigurableApplicationC…

生成测试报告,在Unittest框架中就是简单

测试套件(Test Suite)是测试用例、测试套件或两者的集合,用于组装一组要运行的测试(多个测试用例集合在一起)。 (1)创建一个测试套件: import unittest suite unittest.TestSuite…

车载测试:详解ADAS传感器(相机)标定数据采集方法

1.基本原理 相机外参标定,通过拍摄多角度棋盘格标定相机外参。 2.外参标定板设计 标定板分为垂直标定板和水平标定板,由于地面的水平标定板不容易被检测到,本文采用垂直标定板进行相机标定。 在标定过程中标定板需要和车身坐标成正交状态…

中国人民大学与加拿大女王大学金融硕士——所有的为时已晚都是恰逢其时

你是否有过同样的感觉,工作之余想学点什么又觉得有点晚了,心里反复纠结,总是没个结果。记得在网上看到过一句话,你觉得为时已晚的时候,恰恰是最早的时候。与其在心里反复琢磨,不如去付诸行动。中国人民大学…

超详细,自动化测试-Allure测试报告动态生成用例/标题(实战撸码)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 pytest 结合 allu…

Android-源码分析-MTK平台BUG解决:客户电池NTC功能(移植高低温报警,关机报警功能)---第一天分析与解决

MTK平台BUG解决:客户电池NTC功能 一、概述二、步骤1:实现目的?2:准备工作:机制原理的学习(1)MTK充电温度保护机制(2)MTKthermal高温充电机制 3:定位查找与源码…