C#委托和事件

委托是一种定义方法的类。例如String类定义的是字符串,委托类定义的是方法,进而让方法可以像字符串一样作为参数进行传递。C#中定义委托的语法规则就是在方法的定义上增加一个delegate关键词,如下所示:

public delegate void ClickEventHandler(String msg);

这一行代码定义了一个ClickEventHandler类型的委托,该委托可以封装采用String类作为参数并返回void的方法。

与String类不同的是,可以将多个方法赋给同一个委托(称为多播)。来看个例子。Circle类和Linear类中都定义了一个void draw(String)的静态方法。

public class Circle
    {
        public static void draw(String msg)
        {
            Console.WriteLine(msg+": Draw a circle");
        }
    }

public class Linear
    {
        public static void draw(String msg)
        {
            Console.WriteLine(msg+": Draw a linear");
        }
    }

Button类中有一个ClickEventHandler类型的变量和一个void onClick(String)方法,该方法的业务逻辑是当clickEventHandler中有添加方法时,就调用这些方法。

public class Button
    {
        public ClickEventHandler clickEventHandler;
        public void onClick(String msg)
        {
            if (clickEventHandler != null)
            {
                clickEventHandler(msg);
            }
        }
    }

在Program.cs中给Button类中的clickEventHandler依次添加Circle.draw(String)和Linear.draw(String)方法。那么当Button类中的onClick(String)方法被调用时,clickEventHandler中添加的方法被依次调用。

Button btn = new Button();

btn.clickEventHandler = Circle.draw;
btn.clickEventHandler += Linear.draw;
btn.onClick("Button");
//btn.clickEventHandler("Button");

上方代码运行结果如下:

从上面的代码可见,委托的添加、移除和调用是没有范围限制的。如果一个类型包含一个委托变量,那么在类的外部既可以直接调用这个委托(例如上方代码中被注释掉的那行),也可以给它添加或移除方法。如果把委托变量定义为private的话,在类的外部也没法添加和移除方法了。因此为了不公开委托变量(禁止在外部调用委托),又能在类的外部添加和移除方法,需要对委托进行一定的封装,封装后的委托变量就是事件。

事件的语法规则:access modifier + event + Delegate Class + event name;

例如,把Button类中的委托用事件封装后代码如下:

public class Button
    {
        public event ClickEventHandler click;
        public void onClick(String msg)
        {
            if (click != null)
            {
                click(msg);
            }
        }
    }

那么在Program.cs中:

Button btn = new Button();

btn.click+= Circle.draw;
btn.click += Linear.draw;
btn.onClick("Button");

代码运行的结果跟上方的结果一样。

那么可见,委托和事件最常用的场景是:类中的指定方法被调用时,注册了类中相关事件的方法就会被依次调用。用本例来讲就是,Circle.draw()和Linear.draw()两个方法分别注册了Button.click事件,当Button被点击时(即onClick()被调用时),Circle.draw()和Linear.draw()被依次调用。所以可以称Button类为事件发布者,Circle和Linear为事件注册者/订阅者等。

在C#中,委托和事件的命名有一定的编写规范:

  • 委托类型应以EventHandler结尾;
  • 委托的返回值最好是void,并接受两个输入参数,一个是Object类,表示事件发布者,一个是EventArgs的子类,表示委托方法的参数;
  • 事件应以对应的委托类型去掉EventHandler的名称命名;
  • 事件启动的方法通常以on+事件名 命名,并且该方法通常为虚方法,以便进行扩展。

按照上述规范修改后的代码如下:

public delegate void ClickEventHandler(object sender, ClickEventArgs e);
    public class ClickEventArgs : EventArgs
    {
        public String msg { get; set; }
    }
    public class Button
    {
        public event ClickEventHandler click;
        public virtual void onClick(ClickEventArgs e)
        {
            if (click != null)
            {
                click(this,e);
            }
        }
    }

    public class Circle
    {
        public static void draw(object sender, ClickEventArgs e)
        {
            Console.WriteLine(e.msg+": Draw a circle");
        }
    }

    public class Linear
    {
        public static void draw(object sender, ClickEventArgs e)
        {
            Console.WriteLine(e.msg+": Draw a linear");
        }
    }
delegate and event
Button btn = new Button();

btn.click+= Circle.draw;
btn.click += Linear.draw;
btn.onClick(new ClickEventArgs() { msg = "Button" });
Program.cs

委托这种使得方法作为参数传递的功能使其在框架中非常实用。框架有两种方式调用使用者的代码,一种是框架中尽量不出现某个具体类型的引用,而是使用抽象化的基类或接口(不知道这算不算依赖注入)。另一种方式是使用委托,将委托作为参数传递给框架,框架通过委托调用方法。根据MS的官方文档,以下情况下,应使用委托:

  • 当使用事件设计模式时
  • 当封装静态方法可取时
  • 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时
  • 需要方便的组合
  • 当类可能需要该方法的多个实现时

以下情况下,应使用接口:

  • 当存在一组可能被调用的相关方法时
  • 当类只需要方法的单个实现时
  • 当使用接口的类想要将该接口强制转换为其他接口或类类型时
  • 当正在实现的方法链接到类的类型或标识时

此外,委托和事件也经常用于异步方式调用中。根据ref4,.NET提供了执行异步操作的三种模式:

1.基于任务的异步模式, task-based asynchronous pattern, TAP,该模式使用单一方法表示异步操作的开始和完成。使用C#中的async和await关键词实现

2.基于事件的异步模式, event-based asynchronous pattern, EAP,是提供异步行为的基于事件的旧模型。建议新开发中不再使用这种模式。

3.异步编程模型模式, asynchronous programming model, APM,使用IAsyncResult接口提供异步行为的旧模型。不建议新的开发使用此模式。

关于任务和异步方法可参加其他博客。

Refs.:

http://www.tracefact.net/tech/009.html

http://www.tracefact.net/tech/029.html

https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/using-delegates

https://learn.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/

《修炼之道:.NET开发要点精讲》

posted @ 2019-06-18 23:09  南风小斯  阅读(1077)  评论(0编辑  收藏  举报