学习c#的第二十三天

目录

C# 委托(Delegate)

委托概述

使用委托

带有命名方法的委托与匿名方法

示例

如何合并委托(多播委托)

示例

如何声明、实例化和使用委托

示例


C# 委托(Delegate)

委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 你可以通过委托实例调用方法。

委托用于将方法作为参数传递给其他方法。 事件处理程序就是通过委托调用的方法。 你可以创建一个自定义方法,当发生特定事件时,某个类(如 Windows 控件)就可以调用你的方法。 下面的示例演示了一个委托声明:

public delegate int PerformCalculation(int x, int y);

可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。 该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。

注意:在方法重载的上下文中,方法的签名不包括返回值。 但在委托的上下文中,签名包括返回值。 换句话说,方法和委托必须具有相同的返回类型。

将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。 可编写一个比较应用程序中两个对象的方法。 该方法可用在排序算法的委托中。 由于比较代码与库分离,因此排序方法可能更常见。

对于类似的方案,已将函数指针添加到 C# 9,其中你需要对调用约定有更多的控制。 使用添加到委托类型的虚方法调用与委托关联的代码。 使用函数指针,可以指定不同的约定。

委托概述

委托具有以下属性:

  1. 类似于C++的函数指针,但是完全面向对象。与C++的函数指针不同,委托会同时封装对象实例和方法。
  2. 允许将方法作为参数进行传递,这使得委托非常灵活,可以用于定义回调方法。
  3. 可以将多个委托链接在一起,例如,可以对一个事件调用多个方法,从而实现多播委托的功能。
  4. 方法不必与委托类型完全匹配,这意味着可以使用变体来适应不同的方法签名。有关详细信息,请参阅使用委托中的变体。
  5. 使用Lambda表达式可以更简洁地编写内联代码块。在某些上下文中,Lambda表达式可以编译为委托类型,从而进一步简化了代码编写。 若要详细了解 lambda 表达式,请参阅 lambda 表达式。

总的来说,委托作为C#中的重要特性,为开发人员提供了一种非常灵活和强大的方法,用于处理回调、事件处理以及动态方法调用等场景。

使用委托

委托是安全封装方法的类型,类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 委托的类型由委托的名称确定。 以下示例声明名为 Callback 的委托,该委托可以封装采用字符串作为参数并返回 void 的方法:

public delegate void Callback(string message);

委托对象通常可采用两种方式进行构造,一种是提供委托将封装的方法的名称,另一种是使用 lambda 表达式。 以这种方式实例化委托后,可以调用该委托。 调用委托会调用附加到委托实例的方法。 调用方传递到委托的参数将传递到该方法,并且委托会将方法的返回值(如果有)返回到调用方。 例如:

// 为委托创建方法
public static void DelegateMethod(string message)
{Console.WriteLine(message);
}
// 实例化委托。
Callback handler = DelegateMethod;// 使用委托。
handler("Hello World");

委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 Delegate,也不能从其派生出自定义类。 由于实例化的委托是一个对象,因此可以作为实参传递或分配给一个属性。 这允许方法接受委托作为参数并在稍后调用委托。 这被称为异步回调,是在长进程完成时通知调用方的常用方法。 当以这种方式使用委托时,使用委托的代码不需要知道要使用的实现方法。 功能类似于封装接口提供的功能。

回调的另一个常见用途是定义自定义比较方法并将该委托传递到短方法。 它允许调用方的代码成为排序算法的一部分。 以下示例方法使用 Del 类型作为参数:

public static void MethodWithCallback(int param1, int param2, Callback callback)
{callback("The number is: " + (param1 + param2).ToString());
}

然后,你可以将上面创建的委托传递到该方法:

MethodWithCallback(1, 2, handler);

并将以下输出接收到控制台:

The number is: 3

以抽象方式使用委托时,MethodWithCallback 不需要直接调用控制台,记住,其不必设计为具有控制台。 MethodWithCallback 的作用是简单准备字符串并将字符串传递到其他方法。 由于委托的方法可以使用任意数量的参数,此功能特别强大。

当委托构造为封装实例方法时,委托将同时引用实例和方法。 委托不知道除其所封装方法以外的实例类型,因此委托可以引用任何类型的对象,只要该对象上有与委托签名匹配的方法。 当委托构造为封装静态方法时,委托仅引用方法。 请考虑以下声明:

public class MethodClass
{public void Method1(string message) { }public void Method2(string message) { }
}

加上之前显示的静态 DelegateMethod,我们现在已有三个 Del 实例可以封装的方法。

调用时,委托可以调用多个方法。 这被称为多播。 若要向委托的方法列表(调用列表)添加其他方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。 例如:

var obj = new MethodClass();
Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;//这两种类型的分配都是有效的
Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

此时,allMethodsDelegate 的调用列表中包含三个方法,分别为 Method1、Method2 和 DelegateMethod。 原有的三个委托(d1、d2 和 d3)保持不变。 调用 allMethodsDelegate 时,将按顺序调用所有三个方法。 如果委托使用引用参数,引用将按相反的顺序传递到所有这三个方法,并且一种方法进行的任何更改都将在另一种方法上见到。 当方法引发未在方法内捕获到的异常时,该异常将传递到委托的调用方,并且不会调用调用列表中的后续方法。 如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。 若要删除调用列表中的方法,请使用减法运算符或减法赋值运算符(- 或 -=)。 例如:

//remove Method1
allMethodsDelegate -= d1;// 删除 d2 时复制 AllMethodsDelegate
Callback oneMethodDelegate = allMethodsDelegate - d2;

由于委托类型派生自 System.Delegate,因此可以在委托上调用该类定义的方法和属性。 例如,若要查询委托调用列表中方法的数量,你可以编写:

int invocationCount = d1.GetInvocationList().GetLength(0);

调用列表中具有多个方法的委托派生自  MulticastDelegate,该类属于 System.Delegate 的子类。 由于这两个类都支持 GetInvocationList,因此在其他情况下,上述代码也将产生作用。

多播委托广泛用于事件处理中。 事件源对象将事件通知发送到已注册接收该事件的接收方对象。 若要注册一个事件,接收方需要创建用于处理该事件的方法,然后为该方法创建委托并将委托传递到事件源。 事件发生时,源调用委托。 然后,委托将对接收方调用事件处理方法,从而提供事件数据。 给定事件的委托类型由事件源确定。  有关详细信息,请参阅事件。

在编译时比较分配的两个不同类型的委托将导致编译错误。 如果委托实例是静态的 System.Delegate 类型,则允许比较,但在运行时将返回 false。 例如:

delegate void Callback1();
delegate void Callback2();static void method(Callback1 d, Callback2 e, System.Delegate f)
{// Compile-time error.//Console.WriteLine(d == e);// OK at compile-time. False if the run-time type of f// is not the same as that of d.Console.WriteLine(d == f);
}

带有命名方法的委托与匿名方法

委托可以与命名方法相关联。 使用命名方法实例化委托时,该方法作为参数传递,例如:

// 声明一个委托
delegate void WorkCallback(int x);// 定义命名方法
void DoWork(int k) { /* ... */ }// 使用方法作为参数实例化委托
WorkCallback d = obj.DoWork;

这称为使用命名方法。 使用命名方法构造的委托可以封装静态方法或实例方法。 命名方法是在早期版本的 C# 中实例化委托的唯一方式。 但是,如果创建新方法会造成多余开销,C# 允许你实例化委托并立即指定调用委托时委托将处理的代码块。 代码块可包含 Lambda 表达式或匿名方法。

作为委托参数传递的方法必须具有与委托声明相同的签名。 委托实例可以封装静态方法或实例方法。

注意:尽管委托可以使用 out 参数,但不建议将该委托与多播事件委托配合使用,因为你无法知道将调用哪个委托。

从 C# 10 开始,包含单个重载的方法组具有自然类型。 这意味着编译器可以推断委托类型的返回类型和参数类型:

var read = Console.Read; // 只有一个过载;Func<int>推断
var write = Console.Write; // 错误:多个重载,无法选择

示例

using System;public delegate void MyDelegate(string message);public class Program
{public static void Main(){// 使用命名方法创建委托实例MyDelegate delegate1 = new MyDelegate(Method1);delegate1("来自命名方法的Hello");// 使用匿名方法创建委托实例MyDelegate delegate2 = delegate (string message){Console.WriteLine("匿名方法: " + message);};delegate2("来自匿名方法的Hello");}// 命名方法public static void Method1(string message){Console.WriteLine("方法1: " + message);}
}

在这个示例中,我们首先定义了一个名为MyDelegate的委托类型,它接受一个字符串参数并无返回值。然后,在Main方法中,我们创建了两个委托实例delegate1和delegate2。

其中,delegate1使用命名方法Method1来初始化,并调用delegate1来触发委托,从而调用关联的命名方法。

delegate2使用匿名方法来初始化,并调用delegate2来触发委托,从而调用关联的匿名方法。

如何合并委托(多播委托)

使用+运算符可以将多个委托对象分配到一个委托实例中,从而创建一个多播委托。这个多播委托包含了一个已经分配好的委托列表,调用多播委托时会依次调用列表中的委托。

只有类型相同的委托实例才能通过使用+运算符进行合并,否则会产生编译错误。

另外,- 运算符也可用于从多播委托中删除组件委托。例如,如果我们有一个多播委托delegateChain,其中包含了两个委托对象delegate1和delegate2,我们可以使用 - 运算符将其中一个委托对象从多播委托中删除:

delegateChain = delegateChain - delegate1; // 从多播委托中删除 delegate1

这将从delegateChain中删除delegate1,使得delegateChain中仅包含delegate2委托对象。

示例

using System;public delegate void MyDelegate(string message);class Program
{static void Main(){MyDelegate delegate1 = new MyDelegate(Method1);MyDelegate delegate2 = new MyDelegate(Method2);MyDelegate delegate3 = new MyDelegate(Method3);// 合并多个委托对象到一个多播委托MyDelegate delegateChain = delegate1 + delegate2 + delegate3;// 调用多播委托,将依次调用列表中的委托delegateChain("第一次依次调用列表中的委托");Console.WriteLine();// 从多播委托中删除一个委托对象delegateChain = delegateChain - delegate2;// 再次调用多播委托,将不再调用已删除的委托对象delegateChain("第二次依次调用列表中的委托");}static void Method1(string message){Console.WriteLine("Method1: " + message);}static void Method2(string message){Console.WriteLine("Method2: " + message);}static void Method3(string message){Console.WriteLine("Method3: " + message);}
}

运行结果如下所示:

Method1: 第一次依次调用列表中的委托
Method2: 第一次依次调用列表中的委托
Method3: 第一次依次调用列表中的委托Method1: 第二次依次调用列表中的委托
Method3: 第二次依次调用列表中的委托

在这个示例中,我们定义了三个不同的委托对象delegate1、delegate2和delegate3,它们都指向不同的方法。我们使用+运算符将它们合并为一个多播委托delegateChain,并调用它来依次调用列表中的委托。

然后,我们使用-运算符从delegateChain中删除了一个委托对象delegate2。再次调用delegateChain时,已删除的委托对象delegate2不再被调用。

如何声明、实例化和使用委托

可以使用以下任一方法声明委托:

  • 使用匹配签名声明委托类型并声明方法。
  • 将方法组分配给委托类型。
  • 声明匿名方法。
  • 使用 lambda 表达式。有关详细信息,请参阅 Lambda 表达式。

以下是使用不同方法声明委托的示例代码:

using System;// 1. 使用匹配签名声明委托类型并声明方法
public delegate void MyDelegate(string message);class Program
{static void Main(){// 2. 将方法分配给委托类型MyDelegate delegate1 = new MyDelegate(Method1);delegate1("将方法分配给委托类型");// 3. 声明匿名方法并赋给委托MyDelegate delegate2 = delegate (string message) { Console.WriteLine("匿名方法: " + message); };delegate2("声明匿名方法并赋给委托");// 4. 使用 lambda 表达式MyDelegate delegate3 = (message) => { Console.WriteLine("Lambda表达式: " + message); };delegate3("使用 lambda 表达式");}static void Method1(string message){Console.WriteLine("Method1: " + message);}
}

在这个示例中,我们演示了四种不同的方法来声明委托和分配方法:

  1. 我们首先使用匹配签名声明了一个MyDelegate类型的委托,并声明了一个方法Method1,它的签名和委托类型匹配。
  2. 然后,我们将命名方法Method1分配给了delegate1委托对象,并调用它。
  3. 接着,我们声明了一个匿名方法,并将其分配给了delegate2委托对象,并调用它。
  4. 最后,我们使用lambda表达式声明了一个方法,并将其分配给了delegate3委托对象,并调用它。

示例

以下是一个简单的示例,演示如何在C#中声明、实例化和使用委托:

using System;// 声明一个委托类型,它接受两个整数参数并返回一个整数
public delegate int MyDelegate(int x, int y);class Program
{static void Main(){// 实例化委托对象并将其分配给命名方法MyDelegate delegate1 = new MyDelegate(Method1);// 使用委托对象调用已分配的方法int result1 = delegate1(10, 5);Console.WriteLine("结果1: " + result1);// 重新分配委托对象到匿名方法MyDelegate delegate2 = delegate (int x, int y) { return x - y; };// 使用委托对象调用已分配的匿名方法int result2 = delegate2(10, 5);Console.WriteLine("结果2: " + result2);// 重新分配委托对象到 lambda 表达式MyDelegate delegate3 = (x, y) => x * y;// 使用委托对象调用已分配的 lambda 表达式int result3 = delegate3(10, 5);Console.WriteLine("结果3: " + result3);}// 命名方法,它与 MyDelegate 委托类型具有相同的签名static int Method1(int x, int y){return x + y;}
}

在这个示例中,我们首先声明了一个委托类型MyDelegate,它接受两个整数参数并返回一个整数。然后,我们实例化了三个委托对象,并将它们分配给不同的方法、匿名方法和lambda表达式。我们使用这些委托对象调用已分配的方法、匿名方法和lambda表达式,并输出结果。

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

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

相关文章

公网环境固定域名异地远程访问内网BUG管理系统

文章目录 前言1. 本地安装配置BUG管理系统2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射本地服务3. 测试公网远程访问4. 配置固定二级子域名4.1 保留一个二级子域名5.1 配置二级子域名6. 使用固定二级子域名远程 前言 BUG管理软件,作为软件测试工程师的必备工具之一。在…

浅谈 JVM GC 收集器--系列(一)

又到一年大促时刻&#xff0c;今天我们一起探讨下JVM垃圾回收的问题&#xff0c;写代码的时候想一想如何减少FullGC问题的出现&#xff0c;因为一旦出现频繁FullGC&#xff0c;短时间内没有太好的解决办法&#xff0c;很有可能重启后服务接着FullGC&#xff0c;导致服务可用率降…

【探索嵌入式虚拟化技术与应用】— 虚拟化技术深入浅出自学系列

&#x1f308;个人主页: Aileen_0v0&#x1f525;系列专栏:【探索嵌入式虚拟化技术与应用】&#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 一、虚拟技术的发展历史 1.1传统技术的局限性&#xff1a; ​编辑 1.2云计算和万物互联技术的发展机遇&#x…

万宾科技智能井盖传感器,预防城市道路安全

随着城市交通的不断发展和城市化进程的加速推进&#xff0c;城市道路安全问题日益凸显。市政井盖作为城市道路的一部分&#xff0c;承担着重要的交通安全保障职责。然而传统的市政井盖管理方式存在许多不足。针对这些问题政府需要采取适当的措施&#xff0c;补足传统管理方式的…

react原理及合成事件原理

文章目录 react的理解react创建组件的三种写法react的工作原理初始化的渲染流程。页面更新的流程。diffing 算法计算更新视图diff策略 react合成事件原理一、React合成事件的概念二、React合成事件的原理三、React合成事件的优势四、React合成事件的使用方法五、总结 react的理…

小型机加工工厂MES系统选型指南

随着制造业的不断发展&#xff0c;越来越多的企业开始关注生产过程的管理和优化。对于小型机加工工厂来说&#xff0c;选择一款合适的MES系统&#xff08;制造执行系统&#xff09;能够显著提高生产效率、降低成本、优化资源利用&#xff0c;从而在激烈的市场竞争中脱颖而出。 …

HTTP之常见问答

1&#xff1a;HTTP/1.1 如何优化&#xff1f; &#xff1a;尽量避免发送 HTTP 请求&#xff1b;通过缓存技术&#xff0c;使用请求的 Etag 参数来处理判断缓存过期等问题&#xff0c;类似304状态码就是告诉客户端&#xff0c;缓存有效还能继续使用 &#xff1a;在需要发送 HTTP…

创建域名邮箱邮件地址的方法与步骤

如何创建域名邮箱邮件地址?使用Zoho Mail创建域名邮箱邮件地址的步骤简单易懂&#xff0c;操作便捷。从其他邮箱迁移到Zoho Mail的过程也相当顺畅&#xff0c;您可以轻松为所有员工创建具有企业邮箱域名的电子邮件地址。 步骤1&#xff1a;添加并验证您的域名 首先&#xff0c…

rook-ceph部署

rook是云原生存储编排器&#xff0c;本身不提供存储。 下载 git clone --single-branch --branch v1.11.4 https://github.com/rook/rook.git cd rook/deploy/examples 修改镜像地址images.txt operator方式部署rook kubectl apply -f crds.yaml -f common.yaml -f operator…

动态顺序表

目录 一、动态顺序表结构定义 二、动态顺序表初始化 三、动态顺序表打印 四、动态顺序表尾插 五、封装扩容函数 六、动态顺序表头插 七、动态顺序表的尾删 八、动态顺序表的头删 九、动态顺序表任意位置插入 十、动态顺序表任意位置删除 十一、动态顺序表销毁 十二、…

【计算机网络学习之路】网络基础1

文章目录 前言一. 计算机网络发展局域网和广域网 二. 网络协议三. OSI七层模型四. TCP/IP四层&#xff08;五层&#xff09;模型五. 计算机体系结构与网络协议栈六. 协议形式及局域网通信数据包封装与分用 七. 跨网络通信八. MAC地址与网络通信的理解结束语 前言 本系列文章是…

人工智能带来的各方面影响

近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术在各个领域中的应用越来越广泛&#xff0c;已经开始对我们的生活方式、社会和经济结构产生深远的影响。 1.人工智能家庭化。人工智能技术使我们的生活变得更加便利和智能化。在家庭日常中&#xff0c;智能家居、智能…

什么是深度学习

一、深度学习的发展历程 1.1 Turing Testing (图灵测试) 图灵测试是人工智能是否真正能够成功的一个标准&#xff0c;“计算机科学之父”、“人工智能之父”英国数学家图灵在1950年的论文《机器会思考吗》中提出了图灵测试的概念。即把一个人和一台计算机分别放在两个隔离的房…

【SpringBoot3+Vue3】四【基础篇】-前端(vue基础)

目录 一、项目前置知识 二、使用vscode创建 三、vue介绍 四、局部使用vue 1、快速入门 1.1 需求 1.2 准备工作 1.3 操作 1.3.1 创建html 1.3.2 创建初始html代码 1.3.3 参照官网import vue 1.3.4 创建vue应用实例 1.3.5 准备div 1.3.6 准备用户数据 1.3.7 通过…

C++循环队列(模板类)

C循环队列&#xff08;模板类&#xff09; 一、简介二、具体实现 一、简介 循环队列是一种基于数组实现的队列数据结构&#xff0c;其特点是队尾和队头通过模运算相连&#xff0c;形成一个环形结构。这种设计可以有效地利用数组空间&#xff0c;避免因出队操作导致队列空间的浪…

【SQL】简单博客开发代码

前几天做到一些CMS的题&#xff0c;涉及一些sql的代码审计&#xff0c;于是尝试着自己开发一个连接数据库的博客&#xff0c;加深一遍理解 简单实现了登录&#xff0c;验证&#xff0c;登出&#xff0c;目录&#xff0c;增删改查等功能 下面贴代码 conn.php <?phpsessi…

传输层协议 - TCP(Transmission Control Protocol)

文章目录&#xff1a; TCP 协议关于可靠性TCP 协议段格式序号与确认序号六个标志位16位窗口大小 确认应答&#xff08;ACK&#xff09;机制超时重传机制连接管理机制连接建立&#xff08;三次握手&#xff09;连接终止&#xff08;四次挥手&#xff09;TIME_WAIT 状态CLOSE_WAI…

ifconfig

ifconfig ifconfig 是用于配置和显示系统网络接口信息的命令。以下是 ifconfig 输出中常见的一些术语的解释&#xff1a; 网卡&#xff08;Network Interface Card&#xff0c;NIC&#xff09;&#xff1a;网卡是计算机连接到网络的硬件设备&#xff0c;也称为网络适配器或网卡…

V8引擎隐藏类(VIP课程)

上一章我们讲了V8如何存储的对象&#xff0c;其中提到了隐藏类&#xff0c;这一章我们来看看隐藏类到底做了什么。 为什么要讲V8&#xff1f;&#xff1f;&#xff1f;&#xff1f; 隐藏类是V8引擎在运行时自动生成和管理的数据结构&#xff0c;用于跟踪对象的属性和方法 隐藏…

Transformers库总体介绍

Transformers库是什么 Transformers 库是一个在自然语言处理&#xff08;NLP&#xff09;领域广泛使用的强大库&#xff0c;提供了一套全面的工具和预训练模型&#xff0c;用于执行各种 NLP 任务。以下是 transformers 库提供的主要功能&#xff1a; 1.预训练模型&#xff1a…