C# 委托&事件

委托delegate

C#中的委托用于定义方法签名,包括方法名和参数类型。主要用于编码回调机制。

1、委托的作用

1、实现回调功能(回调简单来说就是函数作为参数传入其他函数,然后在适当的时候由其他函数调用)

委托可以定义一个方法签名类型,但不实现具体逻辑。它可以指向不同的方法,这就实现了回调的概念。例如事件处理使用委托定义了事件类型,但交由开发者实现具体逻辑。

2、连接事件和处理程序

委托定义了事件的类型签名,同时可以记录多个处理方法。这就连接了事件源和事件处理程序。

3、异步编程基础

委托算是一个重要的异步模型组成部分。Async/Await使用委托实现了任务和回调函数的关联。

4、支持多线程编程

通过委托可以将方法放入线程池执行或启动新线程调用,实现多线程编程。

5、解耦调用关系

委托允许在运行时动态改变方法目标。调用者与被调用方法解耦了,这在IoC/DEP中很重要。

6、传递行为

委托允许把方法作为参数进行传递。例如比赛可以把获胜逻辑委托给不同的方法。

7、支持异步流程

委托可以支持一种流程驱动的异步编程模型,定义了任务依赖和执行顺序。

总之,委托作为.NET平台的一种基础设施,它支撑了C#中事件处理、异步编程、多线程以及其他更高级设计模式的实现。

委托主要应用场景

  • 事件处理回调
  • Async/Await任务回调
  • 工作队列中任务执行
  • 中间件处理请求响应

2、委托特点

  • 委托类型定义了方法签名协定,但是不定义方法本身。
  • 委托可以引用实现给定签名的任何方法作为其处理程序。
  • 委托引用可以指向一个或多个方法。

3、委托声明

// 定义了无参无返回值的委托
delegate void EventHandler(); 

// 定义带有int参数和返回string的委托
delegate string ConvertDelegate(int i);

4、使用委托

public void Test(){}

EventHandler handler = Test; 
handler(); // 调用Test方法

5、具体例子

下面通过一个例子来具体展示委托在C#中的使用:

​ 1、定义一个包含两个整数参数和返回整数结果的委托:

delegate int Calculator(int a, int b);

​ 2、定义两个实现上述委托方法签名的方法:

int Add(int a, int b) => a + b;

int Mul(int a, int b) => a * b;

​ 3、创建委托实例并指向具体方法:

Calculator calc = Add;

​ 4、调用委托:

int result = calc(1, 2); // result will be 3

​ 5、可以改变委托指向的方法:

calc = Mul;
int result = calc(2, 3); // result will be 6

​ 6、也可以让一个委托指向多个方法

calc += Mul; 

int result = calc(2, 3); // result will be 6 
result = calc(1, 2); // result will be 2

​ 7、删除方法处理程序:

calc -= Mul;

通过这个例子可以看到,委托定义了一个回调函数签名,但不实现具体逻辑,可以动态地指向不同的方法。这就实现了回调功能;委托是C#异步和事件处理的基础设施。

6、委托的发布\订阅者模式(观察者模式)

发布/订阅模式是一种软件设计模式,可以用于实现软件组件之间的松耦合通信。在这个模式中:

  • 发布者(Publisher)是生产数据或执行任务的对象。(发送事件消息)
  • 订阅者(Subscriber)想要接收发布者发布的数据或消息。(接收并响应)
  • 订阅者通过注册自己的消息处理方法到发布者来接收消息。

以下是使用委托实现发布-订阅模式的基本步骤:

1、定义委托:首先,需要定义一个委托来描述订阅者可以接收的事件或消息的签名。委托可以是系统提供的预定义委托类型,如 ActionFunc,也可以自定义委托类型。

public delegate void MyEventHandler(string message);

2、声明事件或消息发布者:在发布者类中声明一个事件或消息,用于发布事件或消息。

public class Publisher
{
    public event MyEventHandler MyEvent;

    public void PublishMessage(string message)
    {
        if (MyEvent != null)
        {
            MyEvent(message);
        }
    }
}

3、创建订阅者:创建一个订阅者类,并定义订阅者对象接收事件或消息后的行为。

public class Subscriber
{
    public void HandleMessage(string message)
    {
        Console.WriteLine("Received message: " + message);
    }
}

4、订阅事件或消息:在订阅者对象中,使用 += 运算符将订阅者的方法与事件或消息关联起来。

Subscriber subscriber = new Subscriber();
Publisher publisher = new Publisher();
publisher.MyEvent += subscriber.HandleMessage;

5、发布事件或消息:发布者对象通过调用事件或消息的发布方法,触发事件或发送消息。

publisher.PublishMessage("Hello, subscribers!");

在上述示例中,我们首先定义了一个名为 MyEventHandler 的委托,用于描述订阅者可以接收的事件或消息的签名。然后,我们在 Publisher 类中声明了一个 MyEvent 事件,并在 PublishMessage 方法中触发该事件。

接下来,我们创建了一个订阅者对象 subscriber 和一个发布者对象 publisher。通过使用 += 运算符,我们将订阅者的 HandleMessage 方法与发布者的 MyEvent 事件关联起来。

最后,我们通过调用 publisher.PublishMessage 方法,发布了一个消息。由于订阅者已经与该事件关联,订阅者对象的 HandleMessage 方法将被调用,并输出接收到的消息。

使用委托实现的发布-订阅模式可以实现对象之间的松耦合通信,发布者和订阅者之间的关联通过委托的机制来建立。这种模式可以用于很多场景,例如事件驱动编程、GUI 应用程序中的用户交互响应等。

但是委托有一个弊端,它可以使用“=”将所有已经订阅的取消,只保留=后的这一个订阅。为了解决这个弊端,事件event应运而生。

事件event

*事件event是一种特殊的委托,它只能+=,-=,不能直接用=。event在定义类中(发布者)是可以直接=的,但是在其他类中(订阅者)就只能+= -=了,也就是说发布者发布一个事件后,订阅者针对他只能进行自身的订阅和取消。

1、以下是使用事件的基本步骤:

1、定义事件委托:首先,需要定义一个事件委托,它描述了订阅者可以处理的事件的签名。

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

2、声明事件:在发布者类中声明一个事件,使用事件委托作为事件类型。

public class Publisher
{
    public event MyEventHandler MyEvent;

    protected virtual void OnMyEvent(EventArgs e)
    {
        MyEventHandler handler = MyEvent;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public void PublishEvent()
    {
        OnMyEvent(EventArgs.Empty);
    }
}

3、创建订阅者:创建一个订阅者类,并定义订阅者对象处理事件的方法。

public class Subscriber
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by subscriber");
    }
}

4、订阅事件:在订阅者对象中,使用 += 运算符将订阅者的方法与事件关联起来。

Subscriber subscriber = new Subscriber();
Publisher publisher = new Publisher();
publisher.MyEvent += subscriber.HandleEvent;

5、发布事件:发布者对象通过调用事件的发布方法,触发事件。

publisher.PublishEvent();

在上述示例中,我们首先定义了一个名为 MyEventHandler 的事件委托,它描述了订阅者可以处理的事件的签名。然后,在 Publisher 类中声明了一个名为 MyEvent 的事件,并提供了一个受保护的虚拟方法 OnMyEvent,用于触发事件并调用订阅者的处理方法。

接下来,我们创建了一个订阅者对象 subscriber 和一个发布者对象 publisher。通过使用 += 运算符,我们将订阅者的 HandleEvent 方法与发布者的 MyEvent 事件关联起来。

最后,我们通过调用 publisher.PublishEvent 方法,发布了一个事件。由于订阅者已经与该事件关联,订阅者对象的 HandleEvent 方法将被调用,输出相应的处理消息。

使用事件的主要优点是它提供了一种标准化的方式来实现发布-订阅模式,简化了代码的编写,并且提供了更好的可读性和可维护性。此外,事件还支持多个订阅者,使得多个对象可以同时订阅同一个事件并相应地处理。

EventHandler 是一个预定义的委托类型,在 C# 中常用于处理事件。它是一个通用委托,用于处理不带自定义事件数据的事件。

2、事件的异常

在事件驱动的编程模型中,一个发布者可以触发一个事件,并允许多个订阅者注册事件处理方法来响应该事件。

以下是一个示例,展示了一个发布者对应多个订阅者的情况:

public class Publisher
{
    public event EventHandler MyEvent;

    protected virtual void OnMyEvent()
    {
        EventHandler handler = MyEvent;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    public void PublishEvent()
    {
        OnMyEvent();
    }
}

public class Subscriber1
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by Subscriber 1");
    }
}

public class Subscriber2
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by Subscriber 2");
    }
}

public class Program
{
    public static void Main()
    {
        Subscriber1 subscriber1 = new Subscriber1();
        Subscriber2 subscriber2 = new Subscriber2();

        Publisher publisher = new Publisher();
        publisher.MyEvent += subscriber1.HandleEvent;
        publisher.MyEvent += subscriber2.HandleEvent;

        publisher.PublishEvent();
        // 输出:
        // Event handled by Subscriber 1
        // Event handled by Subscriber 2
    }
}

在上述示例中,我们创建了两个订阅者对象 subscriber1subscriber2,它们都具有相同的事件处理方法 HandleEvent

然后,我们创建了一个发布者对象 publisher,并将两个订阅者的事件处理方法注册到发布者的事件 MyEvent 上,使用 += 运算符进行订阅。

当调用 publisher.PublishEvent() 方法时,发布者会触发事件,并通知所有注册的订阅者的事件处理方法。在本例中,两个订阅者的事件处理方法都会被调用,并输出相应的消息。

这样,一个发布者可以同时与多个订阅者进行关联,每个订阅者都可以独立地对事件进行处理。这种 模式允许松耦合的通信和更灵活的系统设计。

如果想让订阅的方法都执行处理的话,必须保证订阅者在订阅内包括订阅内的方法不能抛出异常,否则将不能按订阅执行。

委托Func&Action

FuncAction 都是委托类型,用于定义方法的签名和委托实例的创建。它们在 C# 中常用于传递方法作为参数或将方法作为返回值。

主要区别如下:

  1. 参数和返回值:Func 委托用于具有返回值的方法,而 Action 委托用于没有返回值的方法。
    • Func 委托的最后一个类型参数表示方法的返回值类型,前面的类型参数表示方法的参数类型。例如,Func<int, string> 表示具有一个整数参数并返回一个字符串的方法。
    • Action 委托只表示没有返回值的方法,它的类型参数表示方法的参数类型。例如,Action<int> 表示具有一个整数参数且没有返回值的方法。
  2. 参数数量:Func 委托可以具有从 0 到 16 个参数的方法,而 Action 委托可以具有从 0 到 16 个参数的方法。
    • 例如,Func<int, string, bool> 表示具有一个整数和一个字符串参数,并返回一个布尔值的方法。
    • 例如,Action<int, string> 表示具有一个整数和一个字符串参数,并且没有返回值的方法。
public class Program
{
    public static void Main()
    {
        Func<int, int, int> addFunc = (a, b) => a + b;
        int result = addFunc(2, 3);
        Console.WriteLine(result); // 输出:5

        Action<string> greetAction = name => Console.WriteLine("Hello, " + name);
        greetAction("Alice"); // 输出:"Hello, Alice"
    }
}

在上述示例中,我们定义了一个 Func<int, int, int> 委托类型的变量 addFunc,它表示具有两个整数参数和一个整数返回值的方法。我们使用 lambda 表达式来实现这个方法的逻辑,并将其赋值给 addFunc

然后,我们调用 addFunc(2, 3),得到结果 5,并将结果输出到控制台。

类似地,我们定义了一个 Action<string> 委托类型的变量 greetAction,它表示具有一个字符串参数和没有返回值的方法。我们使用 lambda 表达式来实现这个方法的逻辑,并将其赋值给 greetAction

最后,我们调用 greetAction("Alice"),它输出 “Hello, Alice” 到控制台。

通过使用 FuncAction 委托,我们可以更灵活地传递方法,使代码更为简洁和可读。FuncAction 在很多情况下都可以用作回调参数、LINQ 查询、异步编程等方面的工具。

委托和事件的区别

委托和事件是 C# 中常用的两种机制,用于实现事件驱动的编程模型。它们有一些区别和不同的用途。

委托(Delegate)是一种类型,用于表示对一个或多个具有相同签名的方法的引用。它提供了一种将方法作为参数传递、存储方法引用和调用方法的方式。委托可以用于回调机制、事件处理和异步编程等场景。

事件(Event)是委托的一种特殊用法,它提供了一种机制,用于在类或对象发生特定动作或状态改变时通知其他类或对象。事件通过委托来实现,它定义了事件的签名,允许其他类注册和取消注册事件处理方法。事件的发布者(即包含事件的对象)负责触发事件,而订阅者(即注册了事件处理方法的对象)负责响应事件。

以下是委托和事件的一些对比:

  1. 定义方式:委托可以通过 delegate 关键字定义,而事件是在类中声明委托类型的事件。
  2. 角色和责任:委托可以是公共的,可以由任何类或对象引用和调用。而事件通常是私有的,只能由包含事件的类内部触发,其他类通过订阅事件来响应。
  3. 多播性:委托支持多播(多个方法引用),可以将多个方法添加到委托实例中,一次调用委托实例就会调用所有关联的方法。事件本质上也是委托,因此可以具有多个订阅者。
  4. 使用场景:委托更适合于一对一的通信,例如回调机制、事件处理、命令模式等。事件更适合于一对多的通信,即一个对象触发一个事件,多个对象可以注册事件处理方法来响应。
public delegate void MyDelegate(string message);

public class Publisher
{
    public event MyDelegate MyEvent;

    public void PublishMessage(string message)
    {
        Console.WriteLine("Publishing message: " + message);
        OnMyEvent(message);
    }

    protected virtual void OnMyEvent(string message)
    {
        MyDelegate handler = MyEvent;
        handler?.Invoke(message);
    }
}

public class Subscriber
{
    public void HandleMessage(string message)
    {
        Console.WriteLine("Received message: " + message);
    }
}

public class Program
{
    public static void Main()
    {
        Publisher publisher = new Publisher();
        Subscriber subscriber1 = new Subscriber();
        Subscriber subscriber2 = new Subscriber();

        publisher.MyEvent += subscriber1.HandleMessage;
        publisher.MyEvent += subscriber2.HandleMessage;

        publisher.PublishMessage("Hello, world!");
        // 输出:
        // Publishing message: Hello, world!
        // Received message: Hello, world!
        // Received message: Hello, world!
    }
}

在上述示例中,我们定义了一个委托类型 MyDelegate,它表示具有一个字符串参数和没有返回值的方法。

然后,我们创建了一个发布者对象 publisher 和两个订阅者对象 subscriber1subscriber2

通过 publisher.MyEvent += subscriber1.HandleMessagepublisher.MyEvent += subscriber2.HandleMessage,我们将两个订阅者的方法注册为事件处理方法,订阅了 publisherMyEvent 事件。

当调用 publisher.PublishMessage("Hello, world!") 发布消息时,事件被触发,订阅者的事件处理方法被调用,并输出相应的消息。

这个示例展示了委托和事件的使用,其中委托用于定义方法的签名和委托实例的创建,事件用于通知其他对象发生的特定动作。

总结起来,委托是一种通用的方法引用机制,而事件是一种特殊用途的机制,用于实现发布-订阅模型的事件通知。委托可以用于更广泛的场景,而事件则是委托在特定情况下的应用。

猜你喜欢

转载自blog.csdn.net/weixin_43925768/article/details/133682449