Skip to content

委托和事件

委托入门

什么是委托?

委托是从 Delegate 类派生的 .NET 类型,用于封装方法。 通过封装方法并将其实例化为对象,委托使你能够将方法存储在变量中,将它们作为参数传递给其他方法,并在以后调用它们。

委托的签名定义了可以分配给它的方法的参数和返回类型。这意味着你可以创建与方法签名匹配的委托类型,然后将具有该签名的任何方法分配给该委托。此功能允许你通过委托调用该方法,即使你在编译时不知道它是哪个方法。

以下示例演示如何定义委托类型:

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

此示例定义了一个名为 PerformCalculation 的委托。 定义 PerformCalculation 委托来表示采用两个 int 参数(x 和 y)并返回 int 的方法。 此委托可用于封装执行不同计算的方法,例如加法、减法、乘法、除法或使用两个参数的更复杂的公式。

例如,此委托可用于封装以下方法:

c#
public class Calculator
{
    public int Add(int x, int y)
    {
        return x + y;
    }

    public int Subtract(int x, int y)
    {
        return x - y;
    }

    public int Multiply(int x, int y)
    {
        return x * y;
    }

    public int Divide(int x, int y)
    {
        if (y == 0)
            throw new DivideByZeroException();
        return x / y;
    }
}

此示例定义一个 Calculator 类,该类包含与委托签名 PerformCalculation 匹配的方法。 可以创建委托的实例并将其分配给不同的方法,从而允许在运行时根据需求调用适当的方法。

下面的代码演示如何使用 PerformCalculation 委托。

c#
public class Program
{
    public static void Main()
    {
        Calculator calculator = new Calculator();

        // Create delegate instances
        PerformCalculation add = new PerformCalculation(calculator.Add);
        PerformCalculation subtract = new PerformCalculation(calculator.Subtract);
        PerformCalculation multiply = new PerformCalculation(calculator.Multiply);
        PerformCalculation divide = new PerformCalculation(calculator.Divide);

        // Call the methods using the delegates
        Console.WriteLine("Addition: " + add(5, 3)); // Output: 8
        Console.WriteLine("Subtraction: " + subtract(5, 3)); // Output: 2
        Console.WriteLine("Multiplication: " + multiply(5, 3)); // Output: 15
        Console.WriteLine("Division: " + divide(5, 3)); // Output: 1
    }
}

此示例创建 Calculator 类的实例,并将方法分配给 PerformCalculation 委托的实例。 然后,使用委托实例调用这些方法,使你能够基于分配的委托执行不同的计算。

为何使用委托?

委托在直接方法调用方面具有多种优势,并解决了与代码灵活性、动态方法调用和类型安全性相关的几个重要问题。

委托提供了许多好处,包括以下项:

  • 灵活性:委托允许将不同的方法作为参数传递,从而基于运行时条件启用动态行为。 在运行时确定要执行的确切操作时,这种灵活性非常有用。
  • 扩展性:使用委托,可以通过添加新操作来轻松扩展功能,而无需修改现有代码。 你可以传递任何与委托签名匹配的方法。
  • 分离:委托将方法调用与方法定义分离,使代码更易于模块化和维护。

委托是 C# 的强大功能,可解决编程中的几个常见问题。 它们提供了一种灵活且类型安全的方法来封装方法、启用动态方法调用、回调方法和多播调用。 通过使用委托,可以编写更可重用且可维护的代码,这些代码可以适应不断变化的要求。

动态方法调用

在运行时动态调用方法的能力是委托的一项强大功能。 动态方法调用允许编写更灵活且可重用的代码,因为你可以将不同的方法作为参数传递,而无需在编译时知道它们的确切实现。

场景:假设你有一个客户列表,你需要按不同的条件(例如名称、帐户类型或客户 ID)对其进行排序。 如果没有委托,则需要为每个条件编写单独的排序方法,从而导致重复且不易于维护的代码。

解决方案:委托允许您将方法作为参数传递,实现动态方法调用。 您可以定义一个与排序方法签名匹配的委托,并在运行时传递不同的比较函数。 这使得代码更加灵活且可重用。

回调方法

在特定操作完成后需要执行某个动作的情况下,需要使用回调函数。 回调模式在异步编程中很常见,你可能希望在长时间运行的任务完成后通知用户或执行其他作。

场景:在异步编程中,一个操作完成后,通常需要执行其他操作。 如果没有委托,则必须将异步操作与后续操作紧密耦合,从而降低灵活性,使代码难以维护。

解决方案:委托支持实现回调方法,可让你动态指定后续操作。 这会将异步作与回调逻辑分离,从而提高灵活性和可维护性。

类型安全

类型安全性是编程的关键方面,可确保调用的方法与预期的签名匹配。 这可以防止运行时错误,并使代码更可靠。

场景:将方法作为参数传递或存储它们以供以后调用时,需要确保方法签名匹配。 如果没有委托,将缺少类型安全性,从而导致潜在的运行时错误和不太可靠的代码。

解决方案:委托提供类型安全的方法引用,确保方法签名与委托签名匹配。 这可以防止运行时错误,并使代码更可靠。

多播调用

多播调用是委托的一项功能,可用于使用单个委托调用多个方法。 此功能在要通知多个事件订阅者发生事件的事件处理方案中非常有用。

场景:在事件处理中,通常需要通知多个订阅者有关发生的情况。 如果没有委托,则必须手动管理订阅者列表并调用其方法,从而导致复杂且容易出错的代码。

解决方案:委托支持多播调用,允许单个委托引用多个方法。 这通过自动管理订阅者列表并按顺序调用方法来简化事件处理。

在 C# 中声明委托的最佳做法

将委托添加到类时,请务必遵循最佳做法,确保代码井然有序、易读且可维护。 下面是有关定义委托的位置的一些准则:

在类文件的顶部定义委托

开发人员通常的做法是在文件顶部声明委托,通常是在命名空间内,但在任何类之外。 此方法可以轻松查找委托定义并了解其用途。 它还允许定义可供同一命名空间下的多个类使用的委托。

以下代码演示如何实现在类外部定义的委托:

c#
namespace MyNamespace

// Define the delegate outside the class
public delegate void MyDelegate(string message);

public class Publisher
{
    // Method that uses the delegate
    public void PublishMessage(MyDelegate del)
    {
        del("Hello from Publisher!");
    }
}

public class Subscriber
{
    public void Subscribe()
    {
        // Create an instance of the Publisher class
        Publisher publisher = new Publisher();

        // Create an instance of the delegate and pass a method to it
        MyDelegate del = new MyDelegate(PrintMessage);

        // Call the method of the Publisher class and pass the delegate
        publisher.PublishMessage(del);
    }

    // Method that matches the delegate signature
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

在类中定义委托

如果委托特定于某个类,并且不打算在类外部使用,则可以在类中定义委托。 这将委托封装在类中,当委托与类的功能密切相关且不打算在该上下文外部使用时,这种封装非常有用。 如果委托是公共的,则同一命名空间中的其他类可以使用该类的实例访问委托。

c#
namespace MyNamespace

public class Publisher
{
    // Define a public delegate
    public delegate void MyDelegate(string message);

    // Method that uses the delegate
    public void PublishMessage(MyDelegate del)
    {
        del("Hello from Publisher!");
    }
}

public class Subscriber
{
    public void Subscribe()
    {
        // Create an instance of the Publisher class
        Publisher publisher = new Publisher();

        // Create an instance of the delegate and pass a method to it
        Publisher.MyDelegate del = new Publisher.MyDelegate(PrintMessage);

        // Call the method of the Publisher class and pass the delegate
        publisher.PublishMessage(del);
    }

    // Method that matches the delegate signature
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

实例化和调用委托的几种方式

可以使用命名方法、方法组转换、匿名方法或 lambda 表达式将委托实例化。 实例化方法的选择取决于特定的用例和所需的可读性和可维护性级别。

使用命名方法将委托实例化

命名方法是使用特定名称定义的,可以重复使用。 如果要创建指向特定方法的委托实例,则使用命名方法非常有用。 命名方法通常在类或结构中定义,可以是静态方法或实例方法。

c#
public delegate void Notify(string message);

public class Program
{
    public static void Main()
    {
        // Create an instance of the delegate using a named method
        Notify notify = new Notify(NotificationService.SendNotification);

        // Invoke the delegate
        notify("Hello, World!");
    }
}

public static class NotificationService
{
    // Method that matches the delegate's signature
    public static void SendNotification(string message)
    {
        Console.WriteLine("Notification sent: " + message);
    }
}

此示例演示如何创建采用字符串参数并返回 void 的委托类型 Notify。 方法 SendNotification 与委托的签名匹配,允许将其分配给委托实例 notify。 调用委托时,它会使用提供的消息调用 SendNotification 方法。

使用方法组转换将委托实例化

方法组转换是一种通过直接引用方法而无需显式创建委托实例的速记方式。 如果要创建指向特定方法的委托实例,而无需显式委托构造函数,则使用方法组转换非常有用。 使用方法组转换,可以将方法直接分配给委托类型,而无需显式创建委托实例。 当方法签名与委托的签名匹配时,此功能非常有用,因为它允许更精简、更简练的代码。

c#
public delegate void Notify(string message);

public class Program
{
    public static void Main()
    {
        // Create an instance of the delegate using a method group conversion for a named method
        Notify notifyMethodGroup = NotificationService.SendNotification;

        // Invoke the delegate
        notifyMethodGroup("Hello from Method Group!");

    }
}

public static class NotificationService
{
    // Method that matches the delegate's signature
    public static void SendNotification(string message)
    {
        Console.WriteLine("Notification sent: " + message);
    }
}

此示例演示如何使用方法组转换创建委托实例。 方法 SendNotification 直接分配给委托实例 notifyMethodGroup,允许使用消息调用它。 调用委托时,它会使用提供的消息调用 SendNotification 方法。

使用匿名方法将委托实例化

匿名方法允许您直接定义方法,而无需为其显式命名。 此功能对于短期操作或想要在使用点直接定义行为的情况非常有用。 匿名方法可用于创建委托实例,而无需定义单独的方法。 使用匿名方法可以使代码更简洁,并且可以在某些情况下提高可读性。

c#
public delegate void Notify(string message);

public class Program
{
    public static void Main()
    {
        // Create an instance of the delegate using an anonymous method
        Notify notifyAnonymous = delegate (string message) { Console.WriteLine("Anonymous notification: " + message); };

        // Invoke the delegate
        notifyAnonymous("Hello from Anonymous Method!");

    }
}

此示例演示如何使用匿名方法创建委托实例。 匿名方法 delegate (string message) { Console.WriteLine("Anonymous notification: " + message); } 定义委托内联的行为,使其能够通过消息进行调用。 调用委托时,它会执行匿名方法中定义的代码。

使用 lambda 表达式将委托实例化

Lambda 表达式是定义匿名方法的简明方法。 它们允许创建委托实例,而无需显式定义方法。 此功能对于短期操作或想要在使用点直接定义行为的情况非常有用。

c#
public delegate void Notify(string message);

public class Program
{
    public static void Main()
    {
        // Create an instance of the delegate using a lambda expression
        Notify notifyLambda = (message) => Console.WriteLine("Lambda notification: " + message);

        // Invoke the delegate
        notifyLambda("Hello from Lambda!");
    }
}

此示例演示如何使用 lambda 表达式创建委托实例。 lambda 表达式 (message) => Console.WriteLine("Lambda notification: " + message) 定义委托内联的行为,使其能够通过消息进行调用。 调用委托时,它会执行 lambda 表达式中定义的代码。

调用单播和多播委托

可按常规方法那样调用委托。 调用委托时,它会调用所指向的方法,并传递在委托签名中指定的任何参数。 调用委托与直接调用某个方法相似。

可以组合委托来创建多播委托,从而支持在单个调用中调用多个方法。 如果要执行多个操作(例如通知多个事件订阅者或按特定顺序执行多个方法),则多播功能非常有用。

可以使用 + 运算符将多个对象分配给一个委托实例。 多播委托包含已分配的委托的列表。 在调用多播委托时,它会按顺序调用列表中的委托。 只能组合同一类型的委托。

- 运算符可用于从多播委托中删除一个委托组件。

以下示例演示如何使用 + 运算符合并委托,以及如何使用 - 运算符删除委托:

c#
using System;

namespace DelegateExamples;

// Define a custom delegate that has a string parameter and returns void.
delegate void CustomCallback(string s);

class TestClass
{
    // Define two methods that have the same signature as CustomCallback.
    static void Hello(string s)
    {
        Console.WriteLine($"  Hello, {s}!");
    }

    static void Goodbye(string s)
    {
        Console.WriteLine($"  Goodbye, {s}!");
    }

    static void Main()
    {
        // Declare instances of the custom delegate.
        CustomCallback hiDel, byeDel, multiDel, multiMinusHiDel;

        // Initialize the delegate object hiDel that references the
        // method Hello.
        hiDel = Hello;

        // Initialize the delegate object byeDel that references the
        // method Goodbye.
        byeDel = Goodbye;

        // The two delegates, hiDel and byeDel, are combined to
        // form multiDel.
        multiDel = hiDel + byeDel;

        // Remove hiDel from the multicast delegate, leaving byeDel,
        // which calls only the method Goodbye.
        multiMinusHiDel = (multiDel - hiDel)!;

        Console.WriteLine("Invoking delegate hiDel:");
        hiDel("Elize Harmsen");

        Console.WriteLine("Invoking delegate byeDel:");
        byeDel("Mattia Trentini");

        Console.WriteLine("Invoking delegate multiDel:");
        multiDel("Peter Zammit");

        Console.WriteLine("Invoking delegate multiMinusHiDel:");
        multiMinusHiDel("Lennart Kangur");
    }
}

/* Output:
Invoking delegate hiDel:
  Hello, Elize Harmsen!
Invoking delegate byeDel:
  Goodbye, Mattia Trentini!
Invoking delegate multiDel:
  Hello, Peter Zammit!
  Goodbye, Peter Zammit!
Invoking delegate multiMinusHiDel:
  Goodbye, Lennart Kangur!
*/

此示例演示如何使用 + 运算符组合两个委托(byeDel 和 hiDel)来创建多播委托。 在调用时,组合委托(multiDel)同时调用这两种方法。 - 运算符用于从多播委托中删除其中一个委托,只剩另一个委托可调用。

输出中显示调用每个委托的结果,展示多播委托的运作原理。

创建并管理事件

了解事件

事件让程序的一部分在发生重要事件时通知其他部分。发送事件的部分称为发布者,响应事件的部分称为订阅者。例如,事件通常用于具有图形用户界面的程序中,以对按钮或菜单选择之类的事情做出反应。

下图演示了事件驱动模型中发布者与订阅者之间的关系:

publisher-subscriber

(图片来源:官网)

  • 发布者在发生特定操作(例如选择按钮)时引发事件。
  • 订阅者通过执行各自的事件处理程序方法来处理事件。

此模型可确保发布者订阅者保持松散耦合,从而实现更大的灵活性和可维护性。

事件的关键属性

  • 发布者决定何时引发事件,订阅者决定采取什么行动进行响应。
  • 一个事件可以有多个订阅者,一个订阅者可以处理多个事件。
  • 没有订阅者的事件永远不会引发。

示例:一个简单的按钮点击事件

c#
public class Button
{
    public event EventHandler? Selected; // Nullable to indicate no subscribers initially

    public void OnClick()
    {
        // If subscribers exist, ?.Invoke syntax triggers an event
        Selected?.Invoke(this, EventArgs.Empty);
    }
}

// Subscribing to the event
Button button = new Button();
button.Selected += (sender, e) => Console.WriteLine("Button Selected!");

// Triggering the event
button.OnClick(); // Output: "Button Selected!"

在此示例中,使用可为 null 的引用类型将 Selected 事件声明为 EventHandler?,该引用类型显式指示事件可能没有任何订阅者,从而确保代码正常处理此类情况。 ?.Invoke 语法进一步确保仅在存在订阅者时引发该事件,从而阻止潜在的 NullReferenceException 错误。

订阅者使用 Lambda 表达式(=>)来内联定义事件处理程序。Lambda 表达式提供了一种简洁易读的方式来定义事件处理程序,特别是在逻辑简单且不需要单独方法的情况下。

委托:事件的基础

委托是类型安全的函数指针,允许方法作为参数传递并动态调用。 C# 中的事件建立在委托的基础之上,这些委托定义事件处理程序的方法签名。

委托与事件的关系

  • 委托定义事件处理程序必须匹配的方法签名。
  • 事件使用委托在发生重大事件时通知订阅者。

示例:事件背后的委托

c#
// Define a delegate
public delegate void ButtonClickedHandler(object sender, EventArgs e);

// Use the delegate in an event
public class Button
{
    public event ButtonClickedHandler Clicked;

    public void OnClick()
    {
        Clicked?.Invoke(this, EventArgs.Empty); 
    }
}

事件设计的目标

C# 中的事件设计旨在促进组件之间的最小耦合,同时确保灵活性和易用性。

最小耦合:事件支持事件源和事件接收器之间的交互,而无需紧密的依赖关系。 当组件由不同团队开发或独立更新时,使用事件的最小耦合尤其有用。 轻松订阅和取消订阅:订阅 (+=) 和取消订阅 (-=) 的语法非常简单。 支持多个订阅者:事件可以有零个、一个或多个订阅者,使其适用于各种场景。

在 C# 中声明事件

声明事件

事件使类或对象能够在有趣的事情发生时通知其他类或对象。 发布服务器是定义和引发事件的类,而订阅者是附加用于处理事件的方法的类。 在 C# 中,event 关键字用于声明事件,确保只有发布者类可以引发事件,而订阅者可以附加可执行代码来响应它。

示例:声明和引发事件

c#
public class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(this, e);
    }

    public void Increment(int value, int threshold)
    {
        if (value >= threshold)
        {
            OnThresholdReached(EventArgs.Empty);
        }
    }
}

// Subscribing to the event
Counter counter = new Counter();
counter.ThresholdReached += (sender, e) => Console.WriteLine("Threshold reached!");

// Triggering the event
counter.Increment(10, 5); // Output: "Threshold reached!"

在继承中使用事件

如果要跨多个类共享事件功能,可以在基类中声明事件并允许派生类引发或处理它们。 此方法可促进代码重用并简化维护。

示例:在基类中声明和引发事件

c#
public class BaseCounter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(this, e);
    }

    public void Increment(int value, int threshold)
    {
        if (value >= threshold)
        {
            OnThresholdReached(EventArgs.Empty);
        }
    }
}

public class AdvancedCounter : BaseCounter
{
    protected override void OnThresholdReached(EventArgs e)
    {
        Console.WriteLine("AdvancedCounter: Threshold reached!");
        base.OnThresholdReached(e);
    }
}

// Usage
AdvancedCounter counter = new AdvancedCounter();
counter.ThresholdReached += (sender, e) => Console.WriteLine("Subscriber: Threshold reached!");
counter.Increment(10, 5);
// Output:
// AdvancedCounter: Threshold reached!
// Subscriber: Threshold reached!

此示例演示了派生类如何替代基类的事件引发逻辑,同时仍通知订阅者。

声明事件的最佳做法

  • 封装:对事件引发方法使用 protected 访问修饰符来确保事件仅在声明类或其派生类中引发事件。
  • 虚拟方法:使用 protected virtual 方法允许派生类自定义事件引发逻辑。
  • 事件命名:对事件使用有意义的名称来明确指示其用途。

在 C# 中订阅和取消订阅事件

订阅事件

订阅事件涉及使用 += 运算符将方法附加到事件。 事件订阅允许每当引发事件时调用该方法。

示例:订阅事件

c#
public class Button
{
    public event EventHandler Clicked;

    public void OnClick()
    {
        Clicked?.Invoke(this, EventArgs.Empty);
    }
}

public class Program
{
    public static void Main()
    {
        Button button = new Button();
        button.Clicked += Button_Clicked;

        button.OnClick(); // Output: "Button was clicked!"
    }

    private static void Button_Clicked(object sender, EventArgs e)
    {
        Console.WriteLine("Button was clicked!");
    }
}

前面的代码演示如何使用 += 运算符订阅事件。 Clicked 事件从 OnClick 方法触发,方法 Button_Clicked 处理该事件。 ?.Invoke 语法可确保仅当存在订阅者时才会引发该事件。

取消订阅事件

取消订阅事件涉及使用 -= 运算符将方法与事件断开连接。 取消订阅事件对于防止内存泄漏非常重要,尤其是在当事件发布者的生命周期比订阅者更长时。

示例:退订活动

c#
public class Button
{
    public event EventHandler Clicked;

    public void OnClick()
    {
        Clicked?.Invoke(this, EventArgs.Empty);
    }
}

public class Program
{
    public static void Main()
    {
        Button button = new Button();
        EventHandler handler = Button_Clicked;

        button.Clicked += handler;
        button.Clicked -= handler;

        button.OnClick(); // No output, as the handler was unsubscribed.
    }

    private static void Button_Clicked(object sender, EventArgs e)
    {
        Console.WriteLine("Button was clicked!");
    }
}

前面的代码演示如何使用 -= 运算符取消事件订阅。 取消订阅后,引发事件时不再调用事件处理程序。

动态管理订阅

在某些情况下,需要根据特定条件动态添加或删除事件处理程序。 动态删除事件处理程序对于管理复杂工作流或确保在任何给定时间仅附加相关处理程序非常有用。

示例:动态订阅管理

c#
public class Button
{
    public event EventHandler Clicked;

    public void OnClick()
    {
        Clicked?.Invoke(this, EventArgs.Empty);
    }
}

public class Program
{
    public static void Main()
    {
        Button button = new Button();
        bool isSubscribed = false;

        EventHandler handler = (sender, e) => Console.WriteLine("Dynamic handler executed!");

        if (!isSubscribed)
        {
            button.Clicked += handler;
            isSubscribed = true;
        }

        button.OnClick(); // Output: "Dynamic handler executed!"

        if (isSubscribed)
        {
            button.Clicked -= handler;
            isSubscribed = false;
        }

        button.OnClick(); // No output, as the handler was unsubscribed.
    }
}

在此示例中,代码包含动态托管的事件订阅。 isSubscribed 标志可确保根据特定条件添加或删除处理程序。

探索 C# 中委托和事件的关系

高级委托-事件关系

委托是类型安全的函数指针,允许方法作为参数传递并动态调用。 换句话说,委托就像一种“协议”,规定方法的签名,使您可以像变量一样传递方法。 事件将委托封装起来,提供一种结构化的方法,以便当有重大事件发生时通知订阅者。 本单元探讨如何使用 EventHandlerEventHandler<T> 等内置委托来简化事件处理。

使用 EventHandler<T> 处理自定义事件数据

EventHandler<T> 委托是一个内置委托,当需要将自定义事件数据传递给订阅者时,它简化了事件处理。它允许您定义包含有关事件的附加信息的事件处理程序。

示例:对自定义事件数据使用 EventHandler<T>

c#
public class OrderEventArgs : EventArgs
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }
}

public class OrderProcessor
{
    // Using EventHandler<T> to define an event with custom event data
    // Nullable to indicate no subscribers initially
    public event EventHandler<OrderEventArgs>? OrderProcessed; 

    protected virtual void OnOrderProcessed(OrderEventArgs e)
    {
        OrderProcessed?.Invoke(this, e);
    }

    public void ProcessOrder(int orderId)
    {
        Console.WriteLine($"Processing order {orderId}...");
        OnOrderProcessed(new OrderEventArgs
        {
            OrderId = orderId,
            OrderDate = DateTime.Now
        });
    }
}

// Subscribing to the event
public class Program
{
    public static void Main()
    {
        OrderProcessor processor = new OrderProcessor();
        processor.OrderProcessed += (sender, e) =>
        {
            Console.WriteLine($"Order {e.OrderId} processed on {e.OrderDate}");
        };

        processor.ProcessOrder(123); // Output: "Processing order 123..."
                                     // Output: "Order 123 processed on [current date and time]"
    }
}

在此示例中,OrderProcessed 事件被声明为 EventHandler<OrderEventArgs>?,并使用可为空的引用类型。 可为空的引用类型明确表示该事件可能没有任何订阅者,确保代码能够优雅地处理这种情况。 该 ?.Invoke 语法可确保只有在有订阅者时才会引发该事件,从而防止潜在的 NullReferenceException 错误。

Lambda 表达式(如在对 OrderProcessed 事件的订阅中所示)是一种简明定义事件处理程序内联的标准方法。

多播委托

多播委托使得可为单个事件调用多个方法。 当多个订阅者需要响应同一事件(例如通知不同的系统或组件)时,多播委托非常有用。

示例:使用多播委托

c#
public class NotificationService
{
    public event EventHandler? NotificationSent; // Nullable to indicate no subscribers initially

    public void SendNotification()
    {
        NotificationSent?.Invoke(this, EventArgs.Empty);
    }
}

public class Program
{
    public static void Main()
    {
        NotificationService service = new NotificationService();

        // Subscribing multiple methods to the event
        service.NotificationSent += (sender, e) => Console.WriteLine("Email notification sent!");
        service.NotificationSent += (sender, e) => Console.WriteLine("SMS notification sent!");

        service.SendNotification();
        // Output:
        // "Email notification sent!"
        // "SMS notification sent!"
    }
}

在此示例中,lambda 表达式 (=>) 用于定义事件的 NotificationSent 事件处理程序。 Lambda 表达式常与委托结合使用,提供一种简洁而易读的方式来定义事件处理程序,特别是在每个处理程序的逻辑简洁明了时。

管理事件的最佳做法

有效地管理事件对于构建可靠且可维护的应用程序至关重要。 下面是一些要遵循的最佳做法:

  • 避免内存泄漏

不再需要某个事件时,应始终取消对该事件的订阅,尤其是在事件发布者的生存周期比订阅者更长的时候。

  • 使用弱引用

如果订阅者的生存期比发布者短,请考虑使用弱引用或 WeakEventManager 避免内存泄漏。

  • 确保线程安全

如果在多线程环境中引发事件,请使用锁或线程安全集合等同步机制确保线程安全。

示例:在 Dispose 中取消订阅以避免内存泄漏

c#
public class Subscriber : IDisposable
{
    private readonly Button _button;

    public Subscriber(Button button)
    {
        _button = button;
        _button.Clicked += Button_Clicked;
    }

    private void Button_Clicked(object sender, EventArgs e)
    {
        Console.WriteLine("Button clicked!");
    }

    public void Dispose()
    {
        _button.Clicked -= Button_Clicked;
    }
}

此代码示例强调如何取消订阅 Dispose 方法中的事件。此方法可确保正确删除事件处理程序,从而在处置对象时防止内存泄漏。

Last updated:

Released under the MIT License.