C# 委托和事件

委托(Delegate)

定义

委托是一种特殊的类型,用于安全地封装一个或多个方法,并将这些方法作为参数进行传递或调用。委托是方法的引用,可以看作是指向方法的指针。

作用
  • 封装方法引用:将方法引用封装在委托对象中,实现方法的间接调用。
  • 回调机制:允许程序在适当的时候回调这些被封装的方法。
使用步骤
  1. 声明委托:定义委托的签名,即方法的返回类型和参数列表。
  2. 编写方法:编写符合委托签名的方法。
  3. 创建委托实例:创建委托类型的变量,并将其与具体的方法关联。
  4. 调用委托:通过委托实例调用其关联的方法,或利用Invoke方法显式调用。
  5. 多播委托:一个委托实例可以关联多个方法,调用时按顺序执行。
示例
delegate void SimpleDelegate(string message);

class Program
{
    static void Main(string[] args)
    {
        SimpleDelegate del = new SimpleDelegate(Method1);
        del += Method2; // 多播委托
        del("Hello, Delegate!");
    }

    static void Method1(string message) { Console.WriteLine(message + " from Method1"); }
    static void Method2(string message) { Console.WriteLine(message + " from Method2"); }
}

事件(Event)

定义

事件是委托的进一步封装,用于在类或对象之间传递消息。事件是类的一部分,通过event关键字声明。

参与者
  • 发布者(Publisher):声明事件和触发事件的类。
  • 订阅者(Subscriber):监听事件并定义事件处理程序的类。
使用步骤
  1. 定义委托:首先定义一个委托类型,用于封装事件处理程序的方法签名。
  2. 定义事件参数类(可选):如果事件需要传递额外信息,定义从EventArgs派生的类。
  3. 定义事件:使用event关键字声明事件,其类型为前面定义的委托。
  4. 添加事件处理程序:订阅者通过+=操作符将事件处理程序添加到事件。
  5. 触发事件:在适当的时候,发布者通过调用委托(通常封装在受保护的OnEventName方法中)来触发事件。
示例
using System;

public class MyEventArgs : EventArgs
{
    public string Message { get; set; }

    public MyEventArgs(string message)
    {
        Message = message;
    }
}

public class Publisher
{
    public event EventHandler<MyEventArgs> MyEvent;

    protected virtual void OnMyEvent(MyEventArgs e)
    {
        MyEvent?.Invoke(this, e);
    }

    public void DoSomething()
    {
        // 触发事件
        OnMyEvent(new MyEventArgs("Hello, Event!"));
    }
}

public class Subscriber
{
    public void HandleMyEvent(object sender, MyEventArgs e)
    {
        Console.WriteLine(e.Message);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Publisher pub = new Publisher();
        Subscriber sub = new Subscriber();

        pub.MyEvent += sub.HandleMyEvent;
        pub.DoSomething(); // 输出: Hello, Event!
    }
}

发布-订阅模型

发布-订阅模型是一种设计模式,其中发布者(或称为生产者)发布消息或事件,而不关心谁接收这些消息。订阅者(或称为消费者)订阅他们感兴趣的消息,并在消息到达时接收和处理它们。在C#中,事件和委托是实现发布-订阅模型的主要机制。发布者通过事件发送消息,而订阅者通过注册事件处理程序来接收这些消息。这种解耦机制使得系统更加灵活和可扩展。


事件和委托的不同点

  1. 赋值与修改限制

    • 委托:可以直接赋值或修改,如 delegateInstance = nulldelegateInstance = newMethod
    • 事件:不能直接赋值或修改(如 eventInstance = null 会导致编译错误),这是为了保护事件的使用,避免外部代码不当地改变事件的行为。事件只能通过 +=-= 操作符来添加或移除事件处理程序。
  2. 调用方式

    • 委托:可以通过 Invoke() 方法或直接使用括号 () 来调用。
    • 事件:没有 Invoke() 方法,只能通过括号 () 的方式来触发事件,但这实际上是调用了事件背后的委托。

委托和事件的选择

  • 一般情况:在大多数普通场景中,使用委托和事件没有本质区别。然而,由于事件提供了额外的封装和保护,推荐使用事件来避免不必要的直接操作。
  • 控件开发:当进行控件或类的二次开发,特别是需要扩展事件时,必须使用事件来保持接口的一致性和安全性。

事件和委托的联系

  • 主要目的:都是为了实现方法的解耦和回调机制,使得代码更加灵活和可扩展。
  • 异步委托调用:类似于将任务委托给第三方处理,并在任务完成后接收结果(回调)。
  • 同步委托调用:类似于将任务委托给内部执行,并立即或稍后获取结果。

事件和观察者模式的联系

  • 观察者模式:是一种设计模式,用于建立对象之间的一对多依赖关系。当一个对象(主题/事件源)的状态改变时,它会自动通知所有依赖它的对象(观察者/监听者)。
  • 事件:是观察者模式在.NET中的具体实现。事件源(如控件或自定义类)通过事件来通知观察者(事件处理程序),实现了对象之间的松耦合通信。
  • 关系:事件为观察者模式提供了一种机制,使得对象之间的交互更加灵活和可维护。通过事件,对象可以在不知道彼此具体实现的情况下进行通信,这提高了系统的可扩展性和可维护性。

委托和事件的深入比较

  • 封装性:事件比委托提供了更强的封装性。事件通常被声明为私有字段,并通过公共的添加(+=)和移除(-=)访问器来暴露给外部。这允许类的设计者控制哪些外部代码可以订阅事件,从而减少了类的内部实现与外部代码之间的耦合。

  • 多线程安全:在.NET中,事件的处理通常被设计为线程安全的。当你触发一个事件时,即使多个线程同时尝试添加或移除事件处理程序,.NET运行时也会确保这些操作是安全的。然而,对于自定义的委托调用,你可能需要手动处理多线程同步问题。

委托和事件的使用场景

  • 委托:适用于需要直接调用方法列表(回调列表)的场景,特别是当你需要更精细地控制回调的调用方式(如异步调用、顺序调用等)时。委托也常用于实现高阶函数、策略模式等设计模式。

  • 事件:更适用于基于事件的编程模型,特别是在GUI编程、消息传递系统或任何需要观察者模式的场景中。事件提供了一种发布-订阅机制,使得对象之间可以松散地耦合在一起,从而提高了系统的灵活性和可扩展性。

异步编程中的事件和委托

  • 在异步编程中,委托和事件都可以用于处理异步操作的结果。然而,随着.NET的发展,特别是异步编程模式(APM)、基于任务的异步模式(TAP)和异步方法(async/await)的引入,委托的异步调用变得更加直观和简单。尽管如此,事件仍然在许多异步编程场景中发挥着重要作用,特别是在需要广播异步操作结果给多个监听者时。

委托和事件的性能考虑

  • 在大多数情况下,委托和事件的性能差异是可以忽略不计的。然而,在性能敏感的应用程序中,你应该注意避免不必要的委托调用或事件触发,以减少不必要的开销。此外,对于高频触发的事件,你可能需要考虑使用更高效的事件聚合或节流策略来减少事件处理的次数。

在C#中,委托(Delegate)和事件(Event)是.NET框架中用于实现回调机制的重要概念。它们允许你定义一种类型安全的方法指针,使得你可以在运行时动态地调用方法。

1. Action 和 Func

ActionFunc是.NET Framework 3.5及以后版本中引入的泛型委托,它们简化了委托的使用。

  • Action:表示对参数列表执行操作但不返回任何值的方法。Action可以有0到16个输入参数,但没有返回值。

    定义(以ActionAction<T>为例):

    public delegate void Action();
    public delegate void Action<in T>(T obj);
    

    使用

    Action noParamAction = () => Console.WriteLine("No parameters");
    Action<string> paramAction = (param) => Console.WriteLine(param);
    noParamAction();
    paramAction("Hello, World!");
    
  • Func:表示有返回值的方法。Func可以有0到16个输入参数,并且必须有一个返回值。

    定义(以Func<TResult>Func<T, TResult>为例):

    public delegate TResult Func<out TResult>();
    public delegate TResult Func<in T, out TResult>(T arg);
    

    使用

    Func<int> returnNumber = () => 42;
    Func<string, int> stringLength = (str) => str.Length;
    Console.WriteLine(returnNumber()); // 输出 42
    Console.WriteLine(stringLength("Hello")); // 输出 5
    

2. EventHandler

EventHandler是.NET中用于处理事件的标准委托类型。它定义了一个方法,该方法没有返回值,并接受两个参数:第一个参数是引发事件的对象(通常是object类型),第二个参数是包含事件数据的对象(通常是EventArgs或派生于EventArgs的类型)。

定义

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

使用
通常与事件一起使用,用于定义事件的签名。

使用场景

  • Action 和 Func:当你需要执行无返回值的方法或需要执行有返回值的方法时,使用它们可以使得代码更加简洁和类型安全。它们非常适合用于LINQ查询、异步编程(如Task.Run中的无返回值任务)、以及任何需要回调函数的场景。

  • EventHandler:当你需要定义一个事件,并希望其他类或对象能够订阅并响应这个事件时,使用EventHandler(或其泛型版本EventHandler<TEventArgs>)作为事件的委托类型。这是.NET中处理事件的标准方式。

相关推荐

  1. C# 委托事件

    2024-07-11 14:44:05       10 阅读
  2. C#的委托事件

    2024-07-11 14:44:05       18 阅读
  3. C# 事件委托的区别

    2024-07-11 14:44:05       48 阅读
  4. c#事件委托代码demo

    2024-07-11 14:44:05       24 阅读
  5. C# 委托/事件/lambda

    2024-07-11 14:44:05       46 阅读
  6. C#委托事件

    2024-07-11 14:44:05       24 阅读
  7. C# 委托事件

    2024-07-11 14:44:05       29 阅读
  8. C# - 委托事件

    2024-07-11 14:44:05       12 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 14:44:05       8 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 14:44:05       8 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 14:44:05       7 阅读
  4. Python语言-面向对象

    2024-07-11 14:44:05       10 阅读

热门阅读

  1. MySQL常见的几种索引类型及对应的应用场景

    2024-07-11 14:44:05       10 阅读
  2. 带内管理与带外管理

    2024-07-11 14:44:05       7 阅读
  3. linux 内核 红黑树接口说明

    2024-07-11 14:44:05       9 阅读
  4. 使用Python绘制堆积面积图

    2024-07-11 14:44:05       9 阅读
  5. React@16.x(53)Redux@4.x(2)- action

    2024-07-11 14:44:05       9 阅读
  6. TS-类型别名和接口的区别

    2024-07-11 14:44:05       9 阅读
  7. 索引

    2024-07-11 14:44:05       8 阅读
  8. 嵌入式Bootloader面试题面面观(2万字长文)

    2024-07-11 14:44:05       12 阅读