c#委托与事件

委托

委托是什么

委托本质就是函数的地址,类似c++中的函数指针。
使用委托,可以实现把函数作为参数,在另一个函数中调用。
使用委托可以将函数引用封装在委托对象内。然后调用该委托对象就可以执行委托对象内函数引用指向的函数,而不必在编译时知道将调用哪个函数。
多播委托(委托链):通过+=不断的进行注册方法,实现一次性调用多个方法。

好处是:

  1. 可以在不修改调用方法的前提下,不断的增加业务功能。大大减少程序中逻辑判断的分支
  2. 实现“调用方”与“实现方”的解耦合

委托怎么用

  1. 定义委托,使用delegate关键字
  2. 声明委托实例
  3. 委托方法注册
    • 委托的函数签名必须与将要绑定的方法的签名一致
    • +=添加注册;-=取消注册;直接用= 仅最后注册的方法有效
  4. 调用委托
    • 直接调用
    class UseDelegate
    {
        //2.声明委托
        InformInfoHandler myInform;

        public UseDelegate()
        {
            //3.委托注册
            myInform = new InformInfoHandler(InformA);
            myInform += new InformInfoHandler(InformB);
        }

        public void SendInfo()
        {
            //4.调用委托(直接调用)
            myInform("I am cx");
        }

        private void InformA(string name)
        {
            Console.WriteLine("{0},Hello A", name);
        }
        private void InformB(string name)
        {
            Console.WriteLine("{0},Hello B", name);
        }
    }

委托的底层原理

委托本质上就是一个“类”,能定义类的地方,都可以定义委托。
(可通过反编译软件,进行查看证明,委托就是继承了[mscorlib]System.MulticastDelegate的特殊类)

三个核心方法:BeginInvoke,EndInvoke和Invoke
封送一个委托方法,类似于使用PostMessage进行通信,异步操作,使用EndInvoke方法等待异步操作的完成。
就是一个能存放很多函数指针的调用列表(但函数签名必须和委托类型签名一样),你一调用这个列表,那么列表里的所有的指针所对应的方法依次被执行。
委托对象 obj 在创建时创建了指向方法 SayHi的指针并保存在 _methodPtr中;_target中保存了SayHi方法所在的类的对象(比如我把这段代码写在窗体里按钮的点击方法中,那么此时 _target就是 SayHi方法所在的窗体对象);_invocationList 中保存了追加的两个方法的指针。

委托 vs 函数指针

函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。
在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。
其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。

为什么委托定义的返回值通常都为void?

  • 委托变量可以供多个订阅者注册,如果定义了返回值,那么多个订阅者的方法都会向发布者返回数值,结果就是后面一个返回的方法值将前面的返回值覆盖掉了,因此,实际上只能获得最后一个方法调用的返回值。
  • 发布者和订阅者是松耦合的,发布者根本不关心谁订阅了它的事件、为什么要订阅,更别说订阅者的返回值了,所以返回订阅者的方法返回值大多数情况下根本没有必要。

事件

事件是什么

事件是基于特定类的,用于事件驱动模型的专用委托。
事件本质是对委托的封装,对外提供add_EventName(对应+=)和remove_EventName(对应-=)访问
可以将公有的委托变量定义为私有变量,从而满足类的封装性原则
Event就是封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。

C#中的事件处理实际上是一种具有特殊签名的delegate,象下面这个样子:
public delegate void MyEventHandler(object sender, MyEventArgs e);
其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArgs类用来包含与事件相关的数据,所有的事件参数类都必须从System.EventArgs类派生。当然,如果你的事件不含参数,那么可以直接用System.EventArgs类作为参数。

事件怎么用

声明一个事件
public event InformInfoHandler eveMyInform;

委托 vs 事件(推荐):

事件本质上是委托的一个实例
委托可以定义在类的外部,事件只能定义在类的内部,封装性。
委托你不但可以安排谁是它的调用函数,还可以直接调用它,而事件不能直接调用,只能通过某些操作触发。事件仅仅是供其他类型订阅,而客户端不能直接触发事件。

自定义事件

  1. 定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。
  2. 定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。
  3. 定义事件处理方法,它应当与delegate对象具有相同的参数和返回值类型。
  4. 用event关键字定义事件对象,它同时也是一个delegate对象。
  5. 用+=操作符添加事件到事件队列中(-=操作符移除事件)
  6. 在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,即不能以public方式调用,但可以被子类继承。名字是OnEventName。一个约定俗称的规定,就是订阅事件的方法的命名,通常为“On事件名”
  7. 在适当的地方调用事件触发方法触发事件。
public class EventTest
{
    //1.定义delegate对象
    public delegate void MyEventHandler(object sender, System.EventArgs e);

    //2.(定义事件参数类)省略
    public class MyEventCls
    {
        //3.定义事件处理方法,它与delegate对象具有相同的参数和返回值类型
        public void MyEventFunc(object sender, System.EventArgs e)
        {
            Console.WriteLine("My event is ok!");
        }
    }

    //4.用event关键字定义事件对象
    private event MyEventHandler myevent;

    private MyEventCls myecls;

    public EventTest()
    {
        myecls = new MyEventCls();
        //5.用+=操作符将事件添加到队列中
        this.myevent += new MyEventHandler(myecls.MyEventFunc);
    }

    //6.以调用delegate的方式写事件触发函数
    protected void OnMyEvent(System.EventArgs e)
    {
        if (myevent != null)
            myevent(this, e);
    }
    public void RaiseEvent()
    {
        EventArgs e = new EventArgs();
        //7.触发事件
        OnMyEvent(e);
    }

    public static void Main()
    {
        EventTest et = new EventTest();
        et.RaiseEvent();
    }
}

.Net Framework的编码规范

  • 委托类型的名称都应该以EventHandler结束。
  • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
  • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
  • 继承自EventArgs的类型应该以EventArgs结尾。

委托、事件与Observer设计模式

假设我们有个高档的热水器,我们给它通上电,当水温超过95度的时候:1、扬声器会开始发出语音,告诉你水的温度;2、液晶屏也会改变水温的显示,来提示水已经快烧开了。
热水器由三部分组成:热水器、警报器、显示器。热水器仅仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由警报器发出警报、显示器显示提示和水温。

namespace Delegate
{
    // 热水器
    public class Heater
    {
        private int temperature;
        public string type = "RealFire 001";       // 添加型号作为演示
        public string area = "China Xian";         // 添加产地作为演示
        //声明委托
        public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e);
        public event BoiledEventHandler Boiled; //声明事件

        // 定义BoiledEventArgs类,传递给Observer所感兴趣的信息
        public class BoiledEventArgs : EventArgs
        {
            public readonly int temperature;
            public BoiledEventArgs(int temperature)
            {
                this.temperature = temperature;
            }
        }

        // 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视
        protected virtual void OnBoiled(BoiledEventArgs e)
        {
            if (Boiled != null)
            { // 如果有对象注册
                Boiled(this, e);  // 调用所有注册对象的方法
            }
        }

        // 烧水
        public void BoilWater()
        {
            for (int i = 0; i <= 100; i++)
            {
                temperature = i;
                if (temperature > 95)
                {
                    //建立BoiledEventArgs 对象
                    BoiledEventArgs e = new BoiledEventArgs(temperature);
                    OnBoiled(e);  // 调用 OnBolied方法
                }
            }
        }
    }

    // 警报器
    public class Alarm
    {
        public void MakeAlert(Object sender, Heater.BoiledEventArgs e)
        {
            Heater heater = (Heater)sender;     //这里是不是很熟悉呢?
            //访问 sender 中的公共字段
            Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type);
            Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature);
            Console.WriteLine();
        }
    }

    // 显示器
    public class Display
    {
        public static void ShowMsg(Object sender, Heater.BoiledEventArgs e)
        {   //静态方法
            Heater heater = (Heater)sender;
            Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);
            Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
            Console.WriteLine();
        }
    }

    class Program
    {
        static void Main()
        {
            Heater heater = new Heater();
            Alarm alarm = new Alarm();

            heater.Boiled += alarm.MakeAlert;   //注册方法
            heater.Boiled += (new Alarm()).MakeAlert;      //给匿名对象注册方法
            heater.Boiled += newHeater.BoiledEventHandler(alarm.MakeAlert);    //也可以这么注册
            heater.Boiled += Display.ShowMsg;       //注册静态方法

            heater.BoilWater();   //烧水,会自动调用注册过对象的方法
        }
    }
}

如何让事件只允许一个客户订阅?

方法一:将事件声明为private的,然后提供两个方法来进行注册和取消注册

public class Publishser
{
    // 声明一个私有事件
    private event GeneralEventHandler NumberChanged;//将NumberChanged声明为委托变量还是事件都无所谓了,因为它是私有的
    // 注册事件
    public void Register(GeneralEventHandler method)
    {
        NumberChanged = method;//“=”,而非“+=”,通过这种方式就避免了多个方法注册
    }
    // 取消注册
    public void UnRegister(GeneralEventHandler method)
    {
        NumberChanged -= method;//即使method方法没有进行过注册,也不会抛出异常,仅仅是不会产生任何效果而已。
    }

    public void DoSomething()
    {
        if (NumberChanged != null)
        {
            // 触发事件
            string rtn = NumberChanged();
            Console.WriteLine("Return: {0}", rtn);
        }
    }
}

方法二:C#中提供了一种叫事件访问器(Event Accessor)的东西,它用来封装委托变量。

class Program
{
    static void Main(string[] args)
    {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();

        pub.NumberChanged -= sub1.OnNumberChanged;  // 不会有任何反应
        pub.NumberChanged += sub2.OnNumberChanged;  // 注册了sub2
        pub.NumberChanged += sub1.OnNumberChanged;  // sub1将sub2的覆盖掉了

        pub.DoSomething();// 触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();
// 定义事件发布者
public class Publishser
{
    // 声明一个委托变量
    private GeneralEventHandler numberChanged;
    // 事件访问器的定义
    public event GeneralEventHandler NumberChanged
    {
        add
        {
            numberChanged = value;
        }
        remove
        {
            numberChanged -= value;
        }
    }

    public void DoSomething()
    {
        if (numberChanged != null)
        {
            numberChanged();// 通过委托变量触发事件
        }
    }
}

// 定义事件订阅者
public class Subscriber1
{
    public string OnNumberChanged()
    {
        Console.WriteLine("Subscriber1 Invoked!");
        return "Subscriber1";
    }
}
public class Subscriber2 {/* 与上类同,略 */}

ref:
https://www.cnblogs.com/sjqq/p/6917497.html

猜你喜欢

转载自blog.csdn.net/crayon_chen/article/details/80188938