首页 > 其他分享 >委托(Delegate)和事件(Event)-(下篇)

委托(Delegate)和事件(Event)-(下篇)

时间:2024-12-14 20:27:09浏览次数:7  
标签:订阅 下篇 委托 void 实例 事件 方法 Event Delegate

​​​​​​​委托(Delegate)与事件(Event)-(上篇)-CSDN博客

上一篇内容!

一、创建非静态委托

在C#中,使用非静态委托意味着将委托绑定到类的实例方法上,而不是静态方法。这允许你通过委托调用特定对象上的方法,从而实现更灵活和面向对象的设计。下面我们将详细介绍如何创建、实例化以及调用非静态委托,并讨论它们的应用场景和注意事项。

定义一个委托类型,它可以指向符合该签名的实例方法。

public delegate void ProgressReporter(int percentComplete);

这里我们定义了一个名为 ProgressReporter 的委托类型,它接受一个整数参数并返回 void。接下来,在某个类中定义一个与委托签名匹配的实例方法:

public class Worker {
    public void ReportProgress(int percent) {
        Console.WriteLine($"Progress: {percent}% complete.");
    }
}

在这个例子中,Worker 类包含了一个名为 ReportProgress 的实例方法,它可以作为 ProgressReporter 委托的目标。

实例化非静态委托

需要创建一个非静态委托实例,必须先创建一个包含目标方法的对象实例。然后你可以将该实例的方法赋值给委托变量。以下是几种常见的做法:

直接赋值:最简单的方式是直接给委托变量赋值为实例方法的名字。

Worker worker = new Worker();
ProgressReporter reporter = worker.ReportProgress;

使用 new 关键字:也可以显式地使用 new 来创建委托实例。

ProgressReporter reporter = new ProgressReporter(worker.ReportProgress);

Lambda 表达式:如果实例方法非常简单,可以考虑使用 lambda 表达式来代替命名的方法。

ProgressReporter reporter = (percent) => worker.ReportProgress(percent);

如果一旦创建了委托实例,就可以像调用普通方法一样调用它:

reporter(50); // 输出: Progress: 50% complete.

值得注意的是,当一个实例方法被赋值给委托对象时,这个委托对象不仅要保留着对方法的引用,还要保留方法所属实例的引用。这意味着如果你尝试访问委托的 Target 属性,它将返回原始方法所在的对象实例。对于上面的例子来说,reporter.Target 应该等于 worker 对象。

非静态委托的应用场景

非静态委托特别适合用于那些需要维护状态或依赖于对象内部成员的操作。比如,在事件处理程序中,你可能希望订阅者能够根据自身状态作出反应;或者在一个复杂的业务逻辑里,不同的实体可能会执行相似但又略有差异的行为。此时,非静态委托可以帮助你封装这些行为,并且使得代码更加模块化和易于扩展。

此外,非静态委托还经常出现在跨窗体通信等场合。例如,当你想让一个子窗体修改父窗体的状态时,可以通过定义一个委托并在子窗体上调用相应的方法来实现这一点。这种方式避免了直接暴露父窗体的具体实现细节,同时也保持了良好的解耦性。

注意事项

虽然非静态委托提供了强大的功能,但在使用过程中也有一些需要注意的地方:

生命周期管理:由于非静态委托持有对目标对象的引用,因此如果不小心可能会导致内存泄漏。特别是当委托被长时间持有(如全局事件处理器)而目标对象不再需要时,应该及时解除订阅以释放资源。

线程安全问题:如果多个线程同时操作同一个委托实例,则可能会引发竞态条件。为了确保线程安全,可以在多线程环境中采取适当的同步措施。

委托链中的异常传播:在一个多播委托链中,如果其中一个方法抛出未处理的异常,那么后续的方法将不会被执行,并且异常会被传递给调用者。因此,在设计时应考虑到这一点并适当地处理可能出现的问题

总之,正确地使用非静态委托可以使你的应用程序更加灵活、可维护并且符合面向对象编程的原则。通过理解其工作原理和潜在的风险点,你可以更好地利用这一特性来构建高质量的软件系统。

二、委托使用

委托提供了一种机制,使得方法可以作为参数传递给其他方法,并且可以在需要的时候调用这些方法。委托的应用场景非常广泛,包括但不限于事件处理、回调函数、多线程编程以及 LINQ 表达式等。

实例1

定义一个委托类型 MathOperation,它可以接受两个整数并返回一个整数结果。然后我们将创建不同的方法来执行加法和乘法操作,并将它们绑定到这个委托上。

using System;

namespace MathOperations {
    // 定义委托类型
    public delegate int MathOperation(int x, int y);

    class Program {
        // 加法方法
        static int Add(int a, int b) => a + b;
        
        // 乘法方法
        static int Multiply(int a, int b) => a * b;

        static void Main(string[] args) {
            // 创建委托实例
            MathOperation operation = Add;
            Console.WriteLine($"Addition: {operation(3, 5)}"); // 输出 Addition: 8
            
            // 更改委托实例指向的方法
            operation = Multiply;
            Console.WriteLine($"Multiplication: {operation(3, 5)}"); // 输出 Multiplication: 15
        }
    }
}
实例2

事件是 C# 中的一种特殊形式的委托,它允许对象之间建立发布-订阅模式的关系。下面的例子演示了如何使用委托来创建一个简单的按钮点击事件处理器。

using System;

class Button {
    // 定义委托类型用于表示事件处理程序
    public delegate void ButtonClickEventHandler();
    
    // 定义事件
    public event ButtonClickEventHandler Click;

    // 触发事件的方法
    public void OnClick() {
        Click?.Invoke(); // 如果有订阅者,则调用所有订阅者的处理程序
    }
}

class Program {
    static void Main(string[] args) {
        Button myButton = new Button();
        
        // 订阅事件
        myButton.Click += () => Console.WriteLine("Button clicked!");
        
        // 模拟按钮点击
        myButton.OnClick(); // 输出 Button clicked!
    }
}
实例3

多播委托,这意味着你可以将多个方法链接到同一个委托实例上。当调用该委托时,所有已注册的方法都会被依次执行。

using System;

public delegate void NotificationHandler(string message);

class Program {
    static void NotifyByEmail(string msg) {
        Console.WriteLine($"Email notification: {msg}");
    }

    static void NotifyBySMS(string msg) {
        Console.WriteLine($"SMS notification: {msg}");
    }

    static void Main(string[] args) {
        // 创建单个委托实例
        NotificationHandler notifier = NotifyByEmail;
        
        // 添加另一个方法到委托链
        notifier += NotifyBySMS;
        
        // 调用多播委托
        notifier("New order received."); // 分别输出 Email 和 SMS 的通知信息
    }
}
实例4

为了减少重复定义相似委托类型的需要,C# 提供了几种预定义的泛型委托类型,如 Func<>Action<>。这些泛型委托可以帮助我们更简洁地编写代码。

using System;

class Program {
    static void Main(string[] args) {
        // 使用 Func<> 泛型委托代替自定义委托类型
        Func<int, int, int> add = (a, b) => a + b;
        Console.WriteLine($"Sum: {add(2, 3)}"); // 输出 Sum: 5
        
        // 使用 Action<> 泛型委托表示无返回值的方法
        Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
        greet("World"); // 输出 Hello, World!
    }
}

三、事件的声明

事件(Event)是类或对象用于通知其他类或对象某些事情已经发生的一种机制。事件本质上是委托的一个包装器,它允许一个类(发布者)向一个或多个其他类(订阅者)发送通知。为了声明和使用事件,需要遵循一系列步骤,包括定义委托类型、声明事件本身、触发事件以及订阅/取消订阅事件。

定义委托类型

首先需要定义一个委托类型来指定事件处理程序的方法签名。委托定义了可以作为事件处理程序的方法的参数列表和返回值类型。通常情况下,事件处理程序不返回任何值(即返回类型为 void),并且接受两个参数:一个是事件源对象 (object sender),另一个是包含事件数据的对象 (EventArgs e) 或其派生类。

public delegate void NotifyEventHandler(object sender, EventArgs e);

声明事件

如果一旦有了委托类型,就可以在类中声明事件了。这通常是通过使用 event 关键字完成的。下面是一个简单的示例,展示了如何基于前面定义的委托类型声明一个事件:

public class Publisher {
    // 声明事件
    public event NotifyEventHandler SomethingHappened;
}

在这个代码片段中,Publisher 类声明了一个叫做 SomethingHappened 的事件,它将使用 NotifyEventHandler 作为其委托类型。这意味着所有订阅此事件的方法都必须符合 NotifyEventHandler 所规定的签名。

触发事件

当某个条件满足时,你可以通过调用事件来通知所有订阅者。通常的做法是在类内部创建一个受保护的方法(如 OnSomethingHappened),该方法负责检查是否有任何订阅者,并安全地调用它们。

protected virtual void OnSomethingHappened(EventArgs e) {
    SomethingHappened?.Invoke(this, e); // 确保只有在有订阅者时才调用事件
}

这里使用的 ?.Invoke 语法确保了即使没有任何订阅者也不会抛出异常。

订阅与取消订阅事件

其他类可以通过添加自己的事件处理程序来订阅事件。这通常是通过 += 运算符实现的,而 -= 则用于取消订阅。例如:

// 订阅事件
publisher.SomethingHappened += HandlerMethod;

// 取消订阅
publisher.SomethingHappened -= HandlerMethod;

这里假设 HandlerMethod 是一个符合 NotifyEventHandler 签名的方法。

使用标准.NET事件模式

微软推荐了一套标准化的事件模式,其中事件处理程序总是采用 void EventName(object sender, EventArgs e) 的形式。对于更复杂的情况,你可以使用泛型委托 EventHandler<TEventArgs> 来传递额外的数据。此外,事件参数类应该继承自 EventArgs,以便于识别标准 .NET 事件模式 - C# | Microsoft Learn

字段式事件声明

C# 提供了一种更为简洁的方式来声明事件,称为字段式事件声明(field-like events)。这种方式看起来像是直接声明了一个委托类型的字段,并用 event 关键字修饰,但实际上编译器会自动生成必要的包装逻辑。

 public event EventHandler OrderPlaced;

标签:订阅,下篇,委托,void,实例,事件,方法,Event,Delegate
From: https://blog.csdn.net/C6666888/article/details/144475716

相关文章

  • 一个基于gevent的异步请求库 - grequests
    1.安装pipinstallgrequests-ihttp://mirrors.aliyun.com/pypi/simple/--trusted-hostmirrors.aliyun.com2.基础用法教程用grequests.map()方法时,传入的必须是生成器或列表,下面是用小括号创建的是生成器,用方括号也行,生成列表。importgrequestsimporttimeur......
  • 请求响应(Request-Response)和事件响应(Event-Driven)
    请求响应(Request-Response)和事件响应(Event-Driven)是两种常见的软件和系统设计框架,它们在目的、设计和实现方式上存在明显的区别:请求响应框架目的和概念请求响应框架是一种同步通信模式,常见于客户端-服务器架构中。在这种框架下,客户端发送请求到服务器,服务器经过处理后返回......
  • 【Spring 全家桶】Spring MVC 快速入门,开始web 更好上手(下篇) , 万字解析, 建议收藏 ! ! !
    本篇会加入个人的所谓鱼式疯言❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言而是理解过并总结出来通俗易懂的大白话,小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.......
  • 如何优化实现了ApplicationListener接口导致的onApplicationEvent方法多次调用问题?
    背景:记录一次代码优化,CreateIndex中实现ApplicationListener接口导致onApplicationEvent方法多次调用,方法里重复加载该注解的类.this.applicationContext.getBeansWithAnnotation(ESMetaData.class).排查过程:首先在服务启动run方法打断点,在springboot在加载的过程中,会......
  • 一、locust --events常用
    #*_*coding:utf-8*_*#@Author:zybfromlocustimportTaskSet,task,FastHttpUser,between,events#定义任务集类@events.request.add_listenerdefon_request(request_type,name,context,response,exception,**kwargs):ifrequest_type=="GET"......
  • EventLoop有优点但也有缺点,请说说它的缺点是什么?
    EventLoop虽然是JavaScript并发模型的核心,实现了非阻塞I/O,但也存在一些缺点:无法利用多核CPU:JavaScript本身是单线程的,EventLoop运行在单线程上,这意味着它无法充分利用多核CPU的性能。即使任务被分解成更小的块,它们仍然在一个线程上排队执行。对于CPU密集型任务,这会......
  • requestIdleCallback在EventLoop的什么阶段执行?如何执行?
    requestIdleCallback在浏览器的事件循环(EventLoop)中,空闲阶段(IdlePhase)执行。它会在浏览器完成其他高优先级任务(例如处理用户输入、渲染页面、执行JavaScript等)后,并且有剩余时间时才会被调用。执行方式:注册回调函数:使用requestIdleCallback(callback,options)注册......
  • Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop(二)
    Netty的IO处理循环在Netty中,一个EventLoop需要负责两个工作,第一个是作为IO线程,负责相应的IO操作;第二个是作为任务线程,执行taskQueue中的任务. 接下来我们先从IO操纵方面入手,看一下TCP数据是如何从JavaNIOSocket传递到我们的handler中的.Net......
  • Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop(一)
    目录源码之下无秘密──做最好的Netty源码分析教程Netty源码分析之番外篇JavaNIO的前生今世JavaNIO的前生今世之一简介JavaNIO的前生今世之二NIOChannel小结JavaNIO的前生今世之三NIOBuffer详解JavaNIO的前生今世之四NIOSelector......
  • 举例说明pointer-events有什么实际用途?
    pointer-events在前端开发中非常实用,它控制着元素如何响应指针事件,例如鼠标点击、触摸、悬停等。以下是一些实际应用场景的例子:1.创建不可点击的区域/元素:禁用链接:假设你有一个链接,但在某些情况下你想暂时禁用它,可以使用pointer-events:none;。这将阻止用户点击链接,同......