<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      《CLR Via C# 第3版》筆記之(十二) - 事件

      熟悉C#中的事件機(jī)制,使得我們可以編寫出更加貼近于實(shí)際情況的程序。

      主要內(nèi)容:

      • 本例中事件的場(chǎng)景介紹
      • 事件的構(gòu)造
      • 注冊(cè)/注銷事件
      • 事件在編譯器中的實(shí)現(xiàn)
      • 顯式實(shí)現(xiàn)事件

      1. 本例中事件的場(chǎng)景介紹

      為了更好的介紹事件的機(jī)制,首先我們構(gòu)造一個(gè)使用事件的場(chǎng)景(是我以前面試時(shí)遇到的一個(gè)編程題)。

      具體場(chǎng)景大概是這樣的:某工廠有個(gè)設(shè)備,當(dāng)這個(gè)設(shè)備的溫度達(dá)到90攝氏度時(shí),觸發(fā)警報(bào)器報(bào)警,同時(shí)發(fā)送短信通知相關(guān)工作人員。

      當(dāng)時(shí)我就簡(jiǎn)單的構(gòu)造3個(gè)類:設(shè)備(Equipment),警報(bào)器(Alert),短信裝置(Message)。

      傳統(tǒng)的實(shí)現(xiàn)方法:

      1. 警報(bào)器類(Alert)中編寫一個(gè)報(bào)警的方法(StartAlert),短信裝置類(Message)中編寫一個(gè)發(fā)短信的方法(SendMessage)

      2. 在設(shè)備類(Equipment)中編寫一個(gè)溫度90度時(shí)調(diào)用的方法(SimulateTemperature90)

      3. 在設(shè)備類(Equipment)中引用警報(bào)器類(Alert)和短信裝置類(Message)

      4. 當(dāng)溫度90度時(shí),SimulateTemperature90中會(huì)調(diào)用Alert.StartAlert方法和Message.SendMessage方法來(lái)完成所需功能

      基于事件的實(shí)現(xiàn)方法:

      1. 警報(bào)器類(Alert)中編寫一個(gè)報(bào)警的方法(StartAlert),短信裝置類(Message)中編寫一個(gè)發(fā)短信的方法(SendMessage)

      2. 在設(shè)備類(Equipment)中編寫一個(gè)溫度90度時(shí)調(diào)用的方法(SimulateTemperature90)

      3. 在設(shè)備類(Equipment)中編寫一個(gè)事件(Temperature90)

      4. 警報(bào)器類(Alert)向設(shè)備類(Equipment)注冊(cè)StartAlert,短信裝置類(Message)向設(shè)備類(Equipment)注冊(cè)SendMessage

      5. 當(dāng)溫度90度時(shí),SimulateTemperature90會(huì)觸發(fā)事件(Temperature90),

          此事件會(huì)調(diào)用已注冊(cè)的Alert.StartAlert方法和Message.SendMessage方法來(lái)完成所需功能

      傳統(tǒng)的方法缺陷:

      1. 設(shè)備類(Equipment)關(guān)注警報(bào)器類(Alert)和短信裝置類(Message),當(dāng)警報(bào)器類(Alert)中的方法變更時(shí),

          除了要修改警報(bào)器類(Alert),還必須修改設(shè)備類(Equipment)中調(diào)用警報(bào)器類(Alert)中方法的地方

      2. 增加功能時(shí),比如增加另一個(gè)報(bào)警裝置(Alert2)時(shí),需要修改設(shè)備類(Equipment)中報(bào)警的地方

      基于事件的實(shí)現(xiàn)方法可以很好的改善上述情況:

      1. 警報(bào)器類(Alert)和短信裝置類(Message)關(guān)注設(shè)備類(Equipment),當(dāng)警報(bào)器類(Alert)中的方法變更時(shí),

          只需修改警報(bào)器類(Alert)中注冊(cè)事件的地方,將新的事件注冊(cè)到設(shè)備類(Equipment)的事件(Temperature90)中即可

      2. 增加功能時(shí),比如增加另一個(gè)報(bào)警裝置(Alert2)時(shí),將新的報(bào)警方法注冊(cè)到事件(Temperature90)中即可

      3. 取消警報(bào)器類(Alert)和短信裝置類(Message)事件時(shí),只需在相應(yīng)的類中注銷事件就行,無(wú)需修改設(shè)備類(Equipment)

      2. 事件的構(gòu)造

      下面以事件的方法來(lái)構(gòu)造上述應(yīng)用。

      1. 編寫事件傳遞的參數(shù),暫時(shí)只包含設(shè)備名

      2. 定義事件成員

      3. 定義觸發(fā)事件的方法

      4. 定義模擬觸發(fā)事件的方法

      代碼如下:

      // 編寫事件傳遞的參數(shù),暫時(shí)只包含設(shè)備名
      public class EquipmentEventArgs:EventArgs
      {
          // 設(shè)備名
          private readonly string equipName;
          public string EquipName { get { return equipName; } }
      
          public EquipmentEventArgs(string en)
          {
              equipName = en;
          }
      }
      
      public class Equipment
      {
          // 設(shè)備名
          private readonly string equipName;
          public string EquipName { get { return equipName; } }
      
          public Equipment(string en)
          {
              equipName = en;
          }
      
          // 定義事件成員
          public event EventHandler<EquipmentEventArgs> Temperature90;
      
          // 定義觸發(fā)事件的方法
          protected void OnTemperature90(EquipmentEventArgs e)
          {
              Temperature90(this, e);
          }
      
          // 定義模擬觸發(fā)事件的方法
          public void SimulateTemperature90()
          {
              // 事件參數(shù)的初始化
              EquipmentEventArgs e = new EquipmentEventArgs(this.EquipName);
              // 觸發(fā)事件
              OnTemperature90(e);
          }
      }

      3. 注冊(cè)/注銷事件

      定義警報(bào)器類(Alert)和短信裝置類(Message),并在其中實(shí)現(xiàn)注冊(cè)/注銷事件的方法。

      代碼如下:

      // 警報(bào)器類
      public class Alert
      {
          // 定義要注冊(cè)的函數(shù),注意此函數(shù)的簽名是與 EventHandler<EquipmentEventArgs>一致的
          private void StartAlert(Object sender, EquipmentEventArgs e)
          {
              Console.WriteLine("Equipment: " + e.EquipName + "'s temperature is 90 now!");
          }
      
          // 向Equipment注冊(cè)事件
          public void Register(Equipment equip)
          {
              equip.Temperature90 += StartAlert;
          }
      
          // 向Equipment注銷事件
          public void UnRegister(Equipment equip)
          {
              equip.Temperature90 -= StartAlert;
          }
      }
      
      // 短信裝置類
      public class Message
      {
          // 定義要注冊(cè)的函數(shù),注意此函數(shù)的簽名是與 EventHandler<EquipmentEventArgs>一致的
          private void SendMessage(Object sender, EquipmentEventArgs e)
          {
              Console.WriteLine("Equipment: " + e.EquipName + " sends ‘temperature is 90 now!’ to administrator");
          }
      
          // 向Equipment注冊(cè)事件
          public void Register(Equipment equip)
          {
              equip.Temperature90 += SendMessage;
          }
      
          // 向Equipment注銷事件
          public void UnRegister(Equipment equip)
          {
              equip.Temperature90 -= SendMessage;
          }
      }

      調(diào)用上述事件的代碼如下:

      class CLRviaCSharp_12
      {
          static void Main(string[] args)
          {
              Equipment eq = new Equipment("My Equipment");
              Alert alert = new Alert();
              Message msg = new Message();
      
              // 注冊(cè)Alert和Message的事件后
              Console.WriteLine("=========注冊(cè)Alert和Message的事件后=================");
              alert.Register(eq);
              msg.Register(eq);
              eq.SimulateTemperature90();
      
              // 注銷Alert的事件后
              Console.WriteLine();
              Console.WriteLine("=========注銷Alert的事件后,只有Message事件==========");
              alert.UnRegister(eq);
              eq.SimulateTemperature90();
      
              Console.ReadKey(true);
          }
      }

      調(diào)用結(jié)果如下:

      image

      4. 事件在編譯器中的實(shí)現(xiàn)

      在事件的注冊(cè)/注銷時(shí),我們僅僅是簡(jiǎn)單使用 +=和-=。那么編譯其是如何注冊(cè)/注銷事件的呢。

      原來(lái)編譯器在編譯時(shí)會(huì)自動(dòng)根據(jù)我們定義的公共事件(public event EventHandler<EquipmentEventArgs> Temperature90)

      生成私有字段Temperature90和事件的add和remove方法。通過ILSpy查看上面編譯出的程序集,如下圖:

      image

      使用 +=和-=時(shí),就是調(diào)用編譯器生成的add_***和remove_***方法。

      下面就是Alert類的Registered和UnRegister方法的IL代碼。

      image

      事件是引用類型,那么事件在進(jìn)行注冊(cè)或者注銷時(shí)會(huì)不會(huì)存在線程并發(fā)的問題。比如多個(gè)線程同時(shí)向設(shè)備類(Equipment)注冊(cè)或注銷事件時(shí),會(huì)不會(huì)出現(xiàn)注冊(cè)或注銷不成功的情況呢。

      我們進(jìn)一步觀察add_Temperature90的IL代碼如下

      image

      主要部分就是上圖中的紅色線框部分,可能有些人不太熟悉IL代碼,我將上面的代碼翻譯成C#大致如下:

      public void add_Temperature90(EventHandler<EquipmentEventArgs> value)
      {
          //[0] class [mscorlib]System.EventHandler`1<class cnblog_bowen.EquipmentEventArgs>            
          EventHandler<EquipmentEventArgs> args0;
          //[1] class [mscorlib]System.EventHandler`1<class cnblog_bowen.EquipmentEventArgs>           
          EventHandler<EquipmentEventArgs> args1;
          //[2] class [mscorlib]System.EventHandler`1<class cnblog_bowen.EquipmentEventArgs>           
          EventHandler<EquipmentEventArgs> args2;
          //[3] bool          
          bool args3;
      
          // IL_0000 ~ IL_0006
          args0 = this.Temperature90;
      
          // IL_0007 ~ IL_002d
          do
          {
              // IL_0007 ~ IL_0008
              args1 = args0;
      
              // IL_0009 ~ IL_0015
              args2 = (EventHandler<EquipmentEventArgs>)System.Delegate.Combine(args1, value);
      
              // IL_0016 ~ IL_0023
              args0 = System.Threading.Interlocked.CompareExchange<EventHandler<EquipmentEventArgs>>(ref this.Temperature90, args2, args1);
      
              // IL_0024 ~ IL_002d
              if (args0 != args1)
                  args3 = true;
              else
                  args3 = false;
          }
          while (args3);
      }

      從上面代碼可以看出,添加新的事件后(IL_0009 ~ IL_0015),IL會(huì)繼續(xù)驗(yàn)證原有的事件是否被其他線程修改過( IL_0016 ~ IL_0023)

      所以我們?cè)谧?cè)/注銷事件時(shí)不用擔(dān)心線程安全的問題。

      5. 顯式實(shí)現(xiàn)事件

      從上面的IL代碼,我們看出每個(gè)事件都會(huì)生成相應(yīng)的私有字段和相應(yīng)的add_***和remove_***方法。

      對(duì)于一個(gè)有很多事件的類(比如Windows.Forms.Control)來(lái)說(shuō),將會(huì)生成大量的事件代碼,而我們?cè)谑褂脮r(shí)往往只是使用其中很少的一部分事件。

      這樣使得在創(chuàng)建這些類的時(shí)候浪費(fèi)大量的內(nèi)存。為了避免這種現(xiàn)象和高效率的存取事件委托,我們可以構(gòu)造一個(gè)字典來(lái)維護(hù)大量的事件。

      Jeffery Richard在《CLR via C#》中給了我們一個(gè)很好的例子,為了以后參考方便,摘抄如下:

      using System;
      using System.Collections.Generic;
      using System.Threading;
      
      // 這個(gè)類的目的是在使用EventSet時(shí),提供
      // 多一點(diǎn)的類型安全型和代碼可維護(hù)性
      public sealed class EventKey : Object { }
      
      public sealed class EventSet
      {
          // 私有字典,用于維護(hù) EventKey -> Delegate映射
          private readonly Dictionary<EventKey, Delegate> m_events =
              new Dictionary<EventKey, Delegate>();
      
          // 添加一個(gè)EventKey -> Delegate映射(如果EventKey不存在),
          // 或者將一個(gè)委托與一個(gè)現(xiàn)在EventKey合并
          public void Add(EventKey eventKey, Delegate handler)
          {
              Monitor.Enter(m_events);
              Delegate d;
              m_events.TryGetValue(eventKey, out d);
              m_events[eventKey] = Delegate.Combine(d, handler);
              Monitor.Exit(m_events);
          }
      
          // 從EventKey(如果它存在)刪除一個(gè)委托,并且
          // 在刪除最后一個(gè)委托時(shí)刪除EventKey -> Delegate映射
          public void Remove(EventKey eventKey, Delegate handler)
          {
              Monitor.Enter(m_events);
              // 調(diào)用TryGetValue,確保在嘗試從集合中刪除一個(gè)不存在的EventKey時(shí),
              // 不會(huì)拋出一個(gè)異常。
              Delegate d;
              if (m_events.TryGetValue(eventKey, out d))
              {
                  d = Delegate.Remove(d, handler);
      
                  // 如果還有委托,就設(shè)置新的地址,否則刪除EventKey
                  if (d != null) m_events[eventKey] = d;
                  else m_events.Remove(eventKey);
              }
              Monitor.Exit(m_events);
          }
      
          // 為指定的EventKey引發(fā)事件
          public void Raise(EventKey eventKey, Object sender, EventArgs e)
          {
              // 如果EventKey不在集合中,不拋出一個(gè)異常
              Delegate d;
              Monitor.Enter(m_events);
              m_events.TryGetValue(eventKey, out d);
              Monitor.Exit(m_events);
      
              if (d != null)
              {
                  // 由于字典可能包含幾個(gè)不同的委托類型,
                  // 所以無(wú)法在編譯時(shí)構(gòu)造一個(gè)類型安全的委托調(diào)用。
                  // 因此,我調(diào)用System.Delegate類型的DynamicInvoke方法
                  // 以一個(gè)對(duì)象數(shù)組的形式向它傳遞回調(diào)方法的參數(shù)。
                  // 在內(nèi)部,DynamicInvoke會(huì)向調(diào)用的回調(diào)方法查證參數(shù)的類型安全性,并調(diào)用方法。
                  // 如果存在類型不匹配的情況,DynamicInvoke會(huì)拋出一個(gè)異常。
                  d.DynamicInvoke(new Object[] { sender, e });
              }
          }
      }

      使用EventSet類的方法也很簡(jiǎn)單,修改上面的設(shè)備類(Equipment)如下:

      public class Equipment
      {
          // 設(shè)備名
          private readonly string equipName;
          public string EquipName { get { return equipName; } }
      
          public Equipment(string en)
          {
              equipName = en;
          }
      
          private readonly EventSet m_events = new EventSet();
      
          // 用于標(biāo)示事件類型的Key,當(dāng)有新的事件時(shí),需要再增加一個(gè)Key
          private static readonly EventKey m_eventkey = new EventKey();
      
          // 注冊(cè)/注銷事件
          public event EventHandler<EquipmentEventArgs> Temperature90
          {
              add { m_events.Add(m_eventkey, value); }
              remove { m_events.Remove(m_eventkey, value); }
          }
      
          // 定義觸發(fā)事件的方法
          protected void OnTemperature90(EquipmentEventArgs e)
          {
              m_events.Raise(m_eventkey, this, e);
          }
      
          // 定義模擬觸發(fā)事件的方法
          public void SimulateTemperature90()
          {
              // 事件參數(shù)的初始化
              EquipmentEventArgs e = new EquipmentEventArgs(this.EquipName);
              // 觸發(fā)事件
              OnTemperature90(e);
          }
      }

      其余代碼不用修改,編譯后可正常運(yùn)行。

      EventSet類是針對(duì)事件很多的類來(lái)設(shè)計(jì)的,本例只有一個(gè)事件,這樣做的優(yōu)勢(shì)并不明顯。

      posted @ 2011-07-11 15:06  wang_yb  閱讀(852)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 亚洲日本欧洲二区精品| 国产二区三区不卡免费| 人人狠狠综合久久亚洲爱咲| 成人无码潮喷在线观看| √天堂中文www官网在线| 国产最大成人亚洲精品| 久久精品| 国产小受被做到哭咬床单GV| 国产极品粉嫩尤物一区二区 | 久久精品国产久精国产果冻传媒| 一区二区三区鲁丝不卡| 亚洲肥熟女一区二区三区| 丰满岳乱妇三级高清| 一卡2卡三卡4卡免费网站| 我要看特黄特黄的亚洲黄片| 无码成人精品区在线观看| 亚洲天堂精品一区二区| 亚洲AV无码午夜嘿嘿嘿| 免费av深夜在线观看| 中文字幕有码在线第十页| 国产亚洲色婷婷久久99精品| 精品人妻少妇一区二区三区| 99精品久久精品| 亚洲国产成人综合精品| 和黑人中出一区二区三区| 伊人成人在线视频免费| av无码久久久久不卡网站蜜桃| 国产三级精品三级在线看| 色爱综合激情五月激情| 亚洲十八禁一区二区三区| 玛纳斯县| caoporn免费视频公开| 在线观看视频一区二区三区| 中国女人熟毛茸茸A毛片| 色综合夜夜嗨亚洲一二区| 精品人妻无码一区二区三区性| 日韩无人区码卡1卡2卡| 亚洲国产精品嫩草影院久久| 亚洲人成亚洲人成在线观看| 国产迷姦播放在线观看| 中文字幕精品人妻丝袜|