(12)事件

**多播委托(multicast delegate):一个委托变量可以引用一系列委托,在这一系列委托中,每个委托都顺序指向一个后续的委托,从而形成一个委托链,或者称为多播委托。使用多播委托,可以通过一个方法对象来调用方法链,创建变量来引用方法链,并将那些数据类型用作参数传递给方法。

在C#中,多播委托是一个通用的模式,这个模式称为“observer ”或者publish_subscribe模式,它要应对的是这样一个情形,你需要将单一事件的通知广播给多个订阅者(subscriber)。

**使用多播委托来编码Observer模式。

例子:自动调温器将温度发动给多个订阅者--加热器(Heater)和冷却器(Cooler)。

    //Cooler
    class Cooler
    {
        public Cooler(float temperature)
        {
            Temperature = temperature;
        }

        public float Temperature
        {
            get;
            set;
        }

        public void OnTemperatureChanged(float newTemperature)
        {
            if (newTemperature > Temperature)
            {
                Console.WriteLine("Cooer :on"); 
            }
            else
            {
                Console.WriteLine("Cooer :off");
            }
        }
    }

        //Heater
    class Heater
    {
        public Heater(float temperature)
        {
            Temperature = temperature;
        }

        public float Temperature
        {
            get;
            set;
        }

        public void OnTemperatureChanged(float newTemperature)
        {
            if (newTemperature < Temperature)
            {
                Console.WriteLine("Heater :on");
            }
            else
            {
                Console.WriteLine("Heater :off");
            }
        }
    }

//Thermostat负责向heater和cooler发送温度     public class Thermostat     {         //define the delegate data type,与Heater,Cooler中OnTemperatureChanged的签名匹配。         public delegate void TemperatureChangeHander(float newTemperature);                 //define the event publisher         //OnTemperatureChange存储了订阅者列表。         public TemperatureChangeHander OnTemperatureChange         {             get;             set;         }

        public float CurrentTemperature         {             get { return _CurrentTemperature; }             set             {                 if (value != CurrentTemperature)                 {                     _CurrentTemperature = value;                     //向每个订阅者发出一个通知;但是没有检查NULL值                     //OnTemperatureChange(value);                     //这个简单的赋值修改可以确保在检查空值和发送通知之间,                     //假如所有OnTemperatureChange订阅者都被移出(由一个不同的线程),那么不会触发NullReferenceException异常。                     TemperatureChangeHander localOnChange = OnTemperatureChange;                     if (localOnChange != null)                     {                         //一定要记住,在调用一个委托之前,一定要检查它的值是否是空值。                         localOnChange(value);                     }                 }             }         }         public float _CurrentTemperature;     }

     

   static void Main(string[] args)         {             //连接发布者和订阅者             Thermostat thermostat = new Thermostat();             Heater heater = new Heater(80);             Cooler cooler = new Cooler(60);             string temperature;

            //向OnTemeratureChange委托注册了两个订阅者。             thermostat.OnTemperatureChange += heater.OnTemperatureChanged;             thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;

            Console.WriteLine("Enter temperature:");             temperature = Console.ReadLine();

            thermostat.CurrentTemperature = int.Parse(temperature);             Console.ReadKey();         }

 **将“-=”运算符用于委托会返回一个新实例

委托是一个引用类型,对OnTemperaturechange-=<listener>的任何调用,都不会从OnTemperatureChange删除一个委托而是她的委托比之前少一个。相反,会将一个全新的多播委托指派给他,这不会对原始的多播委托产生任何影响(localOnChange也指向那个原始的多播委托。)

**委托运算符

为了合并Thremostat例子中的两个订阅者,要用“+=”,这样会获取第一个委托,并将第二个委托天机到委托连中,使一个委托指向下一个委托。第一个委托的方法被调用之后,他会调用第二个委托。从委托中删除委托,则要使用“-=”运算符。

**委托运算符的练习

            Thermostat thermostat = new Thermostat();
            Heater heater = new Heater(80);
            Cooler cooler = new Cooler(60);
            //string temperature;


            Thermostat.TemperatureChangeHander delegate1;
            Thermostat.TemperatureChangeHander delegate2;
            Thermostat.TemperatureChangeHander delegate3;

            delegate1 = heater.OnTemperatureChanged;
            delegate2 = cooler.OnTemperatureChanged;

            Console.WriteLine("invoke both delegates:");
            delegate3 = delegate1;
            delegate3 += delegate2;
            delegate3(90);
  
            Console.WriteLine("invoke only delegate2");
            delegate3 -= delegate1;
            delegate3(50);


输出:

invoke both delegates:
Heater :off
Cooer :on
invoke only delegate2
Cooer :off

**运算符+ -的委托运算符,下面是同样成立的。使用赋值运算符,会清除之前的所有订阅者,并允许使用新的订阅者替换他们(不是很理解啊~~委屈。这是委托很容易让人犯错误的一个设计。应注意的事,无论“+”“-”还是复合版本,在内部都是使用静态方法System.Delegate.Combine() 和System.Delegate.Remove()来实现的。这两个方法都获取delegate类型的两个参数。第一个方法Combine()会连接两个参数,将两个委托的调用列表按照顺序连接到一起。第二个方法Remove()则搜索由一第个参数指定的委托链,并删除第二个参数指定的委托。

对于Combine()的两个参数都可以是null ,且返回null。如果一个null,一个非null,则返回非null 的值,这就是为什么thermostat.OnTemperatureChange没有初值时,thermostat.OnTemperatureChange += heater.OnTemperatureChanged;中没有引发异常的原因。

delegate3 = delegate1 + delegate2;

delegate3 = delegate3 - delegate1;

**委托的顺序调用:因为委托能指向里一个委托,后者又能指向其他委托。(链式)

**错误处理:凸显了顺序通知的重要性。假如一个订阅者引发了一个异常,链中的后续订阅者就接收不到通知。

            Thermostat thermostat = new Thermostat();
            Heater heater = new Heater(80);
            Cooler cooler = new Cooler(60);
            string temperature;

            thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
            //Lamda语句异常会使链发生中断,阻碍cooler对象接收通知。
            thermostat.OnTemperatureChange +=
                (newTemperature) =>
                {
                    throw new ApplicationException();
                };
              thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;

            Console.WriteLine("Enter temperature:");
            temperature = Console.ReadLine();

            thermostat.CurrentTemperature = int.Parse(temperature);


为了避免上面的问题,使所有订阅者都能收到通知,必须手动遍历订阅者列表,并单独调用它们。

        public float CurrentTemperature
        {
            get { return _CurrentTemperature; }
            set 
            {
                if (value != CurrentTemperature)
                {
                    _CurrentTemperature = value;
                    
                    if (OnTemperatureChange != null)
                    {   //处理来自订阅者的异常。
                        //从一个委托的GetInvocationList()方法获得一份订阅者列表。
                        foreach (TemperatureChangeHander handler in OnTemperatureChange.GetInvocationList())
                        {   
                            try
                            {
                                handler(value);
                            }
                            catch (Exception exception)
                            {
                                Console.WriteLine(exception.Message);
                            }
                        }
                    }
                }
            }
        

**方法返回值和传引用

涉及的委托要么不返回void,要么有一个ref或out参数,在这种情况下,也有必要遍历委托调用表,而非直接激活一个通知。因为调用了一个通知,就有可能造成通知发送给多个订阅者。假如订阅者会返回值,就无法确定应该使用哪一个订阅者的消息。
为解决上述问题,c#引进了事件。

**事件:

 **事件的作用

1.封装订阅:

            thermostat.OnTemperatureChange += heater.OnTemperatureChanged;          =====》thermostat.OnTemperatureChange = heater.OnTemperatureChanged;

            thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;          =====》 thermostat.OnTemperatureChange = cooler.OnTemperatureChanged;

如果上述代码便成了右侧的代码,则会将heater的订阅取消掉。为了避免上述情况,可以使用event进行外部的封装,部位包容类外部的对象提供赋值运算符的支持。

2.封装发布:事件确保只有包容类才能触发一个事件通知。

static void Main()
{   ...
   //从事件包容者的外部触发事件。
   thermostat.OnTemperatureChange(43);
   ...
}

**事件的声明:C#用event关键字解决了上述两个问题,event定义的是一个新的成员类型。

将thermostat 中的OnTemperatureChange 改成如下:

        //fine the enent publisher
        //用event修饰以后,会禁止为一个public字段使用赋值运算符。
         //只有包容类才能调用向所有订阅者发出通知的委托。
         //例如:不能在类的外部执行 thermostat.OnTemperatureChange(43);
        public event TemperatureChangeHander OnTemperatureChange =
            delegate { };
        //通过赋值一个空委托,代表由0个侦听者构成的集合。就可以引发事件而不必检查是或否有任何监听者。
        //由于重新对委托进行赋值只能在内部,所以如果没有给委托赋值null,则不必每次调用委托时都检查null值。

由于不是很理解书上讲的东西,于是查了其他资料来学习。

namespace evetEx01
{   //1.定义时间前,定义个委托类型,以用于事件
    //使用标准的委托语法,在名称空间中将该委托定义为公共类型。
    public delegate void MessageHandler(Connection source,MessageArrivedEventArgs e);
    public class Connection
    {
        //2.定义委托之后,把事件本身定义为类的一个成员。
        //也可以使用匿名方法定义事件处理方法。
        public event MessageHandler MessageArrived;

        public Timer pollTimer;
        public Connection() 
        {
            pollTimer = new Timer(1000);
            pollTimer.Elapsed+=new ElapsedEventHandler(CheckForMessage);
         }

        public string Name
        {
            get;
            set;
        }
        public void Connect()
        {
            pollTimer.Start();
        }

        public void Disconnect()
        {
            pollTimer.Stop();
        }

        private static Random random = new Random();

        private void CheckForMessage(Object source, ElapsedEventArgs e)
        {
            Console.Write("Check for new Message.");
            if ((random.Next(9) == 0) && (MessageArrived != null))
            {
                //引发一个事件
                MessageArrived(this,new MessageArrivedEventArgs("Hello Mum!"));
            }
        }
    }
    //订阅事件的类
    public class Display
    {
        public void DispalyMessage(Connection source,MessageArrivedEventArgs e)
        {
            Console.WriteLine("Message arrived :{0}",source.Name);
            Console.WriteLine("Message Text{0}",e.Message);
        }
    }
    public class MessageArrivedEventArgs : EventArgs
    {
        private string message;
        public string Message
        {
            get
            {
                return message;
            }
        }   
        public MessageArrivedEventArgs()
        {
            message = "No message sent";
        }

        public MessageArrivedEventArgs(string newMessage)
        {
            message = newMessage;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            Connection myConncetion1 = new Connection();
            myConncetion1.Name = "First Connection";

            Connection myConncetion2 = new Connection();
            myConncetion2.Name = "Second Connection";

            Display myDisplay = new Display();
            //+=订阅事件  
              myConncetion1.MessageArrived+=new MessageHandler(myDisplay.DispalyMessage);
            myConncetion2.MessageArrived += new MessageHandler(myDisplay.DispalyMessage);
            myConncetion1.Connect();
            myConncetion2.Connect();
            Console.ReadKey();
        }                                                                                                                                                                                                                          }
}


**可以自定义add和remove处理程序

 //define the event publish

publish enent TemperatureChangeHandler OnTemperatureChage

{

add

{

System.Delegate.Combine(value,_OnTemperatureChange);

}

remove

{

System.Delegate.Remove(_OnTemperatureChange,value);

}

}

protected TemperatureChangeHander _OnTemperatureChange;

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值