委托(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