.NET 委派 @ 資源回收 :: 隨意窩 Xuite日誌
  • 關鍵字
    1. 沒有新回應!
  • 200909130020.NET 委派

    引用:用故事講事件與委派

    用故事講事件與委派

    .NET 中的事件與委派模型,或許還是有些人不是很瞭解
    ,我想故事的方式、概念性的方式讓人家可以輕鬆了瞭解這個模型,
    以下不會提到任何的程式碼。

    假設我今天要是一個類別,類別名稱叫 經理 (Manager) ,他的父類別叫 員工(Employee)。
    員工這個類別定義簡單的基本屬性及方法

    Employee類別
    屬性:姓名、生日、電話、地址
    方法:算薪資

    經理類別繼承了員工這個類別,不過經理的薪資算法不同,所以複寫了(算薪資)這個方法,
    還有屬於經理這個職務的人,有一個優惠就是只要八點時間一到,並符合一定條件的情況下, 就有人替他到咖啡。

    符合一定條件有兩個,

    第一個是要有職務叫秘書的人
    第二個這個人要會泡咖啡

    兩個條件缺一不可,如果人但沒有秘書這個職務,就沒人替經理泡咖啡;
    如果剛好有人,而這個人不會泡咖啡,也不會有人替經理泡咖啡。

    而這個八點泡咖啡就好比事件

    而符合兩個條件的就好比要符合事件的簽章辦法

    今天 James 經理工作一來一直都沒有秘書可以幫他泡咖啡,所以這個八點泡咖啡的事件,
    一直都沒有被觸發,不過今年公司賺錢,請來一位秘書叫 雪莉, 雪莉在履歷上有提到
    她會泡咖啡,面試的人事室主任小李,看了看她了履歷:好!我就錄取妳了,你除了做好
    一般秘書的例行性的工作之後,並派給她一件工作,請她準時在八點的時候,泡一杯咖啡
    給James經理。

    而經驗老道的雪莉只有一套獨特的泡咖啡方法,首先她會先放一顆特製的方糖,準備研磨好的 貓糞咖啡,之後攪拌28下,最後倒一滴楓糖。

    James之後,只要一到八點,就有一杯美味的楓糖貓糞咖啡可以喝了。

    這個人事室的小李就好比委派,他為何會派這份泡咖啡工作給雪莉,正是因為雪莉符合兩個條件, 所以這兩個條件也就是符合委派的簽名方法,所以小李派遣雪莉每到八點就要泡一杯咖啡給James經理

    而雪莉自有一套泡咖啡的方法,這就好比事件處理常式,當八點一到事件觸發了,所以雪莉用她自己 的處理方法來泡咖啡。

    如果有一天,雪莉辭職了,來了一位新的秘書莉莉,而她也會泡咖啡,小李一樣可以指派泡咖啡的工作 給莉莉,而莉莉自己也一套另類的泡咖啡方法。

    所以經理這個類別定義一個概念,就是八點一到,有符合條件的人會替經理泡咖啡,誰去指派這件工作 ,就交給人事室主任小李去指派。而且以後,泡咖啡的人各有她的方法,所以就有不同的事件處理常式。 就好像我設定一個事件,這個事件要怎麼做、或是做什麼事,由你自己去決定,自由度就大了。

    那之前James經理因為沒有秘書所以就算八點一到,也沒有人替他到咖啡,就好比事件已經定義了,但是
    還沒註冊到方法,所以八點一到事件依然沒有被觸發。

    如果有一天,公司請了兩位秘書,而且都會泡咖啡,小李就派她們兩個,八點一到就泡咖啡給James經理 ,James八點一到就有兩杯不同風味的咖啡可以品嚐。這就好比委派可以多重傳送的功能一樣,事件一觸發 傳給兩個人做。

     


    引用:ASP.NET 2Share V2

    委派的基本概念

    在 .Net Framework 的基礎領域中, 事件處理模型一直是令人頭痛的一環。倒不是因為它真的有什麼難度, 而是因為 .Net Framework 稍嫌麻煩的處理方式, 以及它的一些難懂的特殊用語, 有時候還真的會讓人搞得眼花潦亂, 甚至退避三舍。

    在這裡, 我要試圖使用最簡單、最白話的方式把 .Net Framework 對於事件與委派的設計原理重新描述一遍。我相信如果你很仔細的看過一遍, 就再也不會對這個主題心存疑惑了。

    事件 (Events) -

    當你做了什麼事情 (例如按下某個鍵、按下滑鼠) 或者發生了什麼事情 (例如每隔一百毫秒、或程式發生錯誤) 的時候, 如果你期待能夠做些什麼事情來針對上述狀況進行處理, 那麼這就叫做一個事件。我們通常會說「滑鼠按下左鍵」、「使用者從下拉式選單中選取了某個項目」等等, 這就是事件。

    你可以宣告一個事件, 其語法和宣告其它物件時不太一樣:

    VB -

        Public Event ErrorFound(ErrorCode As Integer)

    在 C# 中事件無法單獨宣告, 而必須和委派一起宣告; 所以我們等到後面再來看看範例。

    在上面範例中, 我們宣告了一個稱為 ErrorFound 的事件。如果你還無法理解這是什麼意思, 那麼你不妨想像一下, 當你在程式中按下滑鼠左鍵時, 系統不是會發出一個 Click 事件嗎? 在這裡, 我們宣告了一個 ErrorFound 事件, 意思是當有錯誤發生時, 會有一個 ErrorFound 事件被激發。

    不過, 如果你光是在程式中宣告了一個事件, 這個事件在任何時候都還是不會被激發, 因為還有一些配套的措施尚未完成。我們繼續看下去。

    事件處理函式 (Events Handler) -

    當事件發生時, 你要使用什麼方式去回應? 例如, 如果使用者按下了 F1 功能鍵, 你是不是會寫程式來因應這種狀況? 例如開啟一個視窗, 然後把說明畫面開出來顯示。又例如, 如果程式突然連不到資料庫, 你又應該採取什麼緊急應變描施 (比方說改連另一個備份資料庫)? 像這種用來因應事件的程式, 就叫做事件處理函式。

    事件處理函式就是一個普通的方法 (Method), 可以是 Sub 或是 Function。隨便任何一個方法 (Method) 都可以被當作事件處理函式 (當然, 傳入的參數必須正確), 所以在這裡我就不另外作範例了。

    委派 (Delegate) -

    我個人認為就是「委派」這個翻譯令人覺得疑惑。「委派」從字面上看起來就像個動詞, 但實際上在 .Net 事件處理上, 這個字通常是以名詞的方式被使用。雖然嚴格的講, 「委派」這個字並沒有翻錯, 但是我倒是希望能額外賦予它更豐富的含意, 例如「委任」、「委任方法」、「委任函式」、「委託」、「代理人」、「代理函式」... 等等。或許我把它以更多的詞彙來描述之後, 你能夠更明白它的內在本質。

    如果以最白話的方式來講, 那麼你可以把這個「委派」想像是事件與事件處理函式中間的一個「跳板」。怎麼說呢? 當一個事件發生時, 你必須知道, 事件處理函式不見得永遠是那一個。即使是同一個事件 (例如使用者按了滑鼠鍵), 你可能視情況把事件交給 A() 處理, 也可能交給 B() 處理...。那麼到底是交給 A() 還是 B() 還是 C()... 你可以讓「委派」(Delegate) 來決定 (經由這個解釋, 我相信你已經更明白為什麼它被稱之為「委派」)。

    範例程式  (VB)-

    以下我們就來看看完整的範例程式:

    (第一種做法) -

        Public Delegate Sub myDelegate(ByVal Code As Integer) ' 宣告委派
        Dim SomeDelegate As New myDelegate(AddressOf ErrorHandler) ' 建立委派的實體 (Instance)
        Public Sub ErrorHandler(ByVal Code As Integer) ' 建立事件處理函式
            ' 在這裡處理事件的因應方法
        End Sub
        Sub Page_Load(...) Handles Me.Load
            SomeDelegate(1) ' 當有錯誤發生時直接呼叫委派實體
        End Sub

    (第二種做法) -

        Public Event ErrorFound(ErrorCode As Integer) ' 宣告事件
        Public Sub ErrorHandler(ByVal Code As Integer)
            ' 在這裡處理事件的因應方法
        End Sub
        Sub Page_Load(...) Handles Me.Load
            AddHandler Me.ErrorFound, AddressOf ErrorHandler ' 將事件和事件處理函式之間建立關聯
            RaiseEvent ErrorFound(1) ' 當有錯誤發生時發動事件
        End Sub

    以上兩個程式可以達成完全相同的結果, 只是第一個做法使用委派而第二個做法不使用委派。

    在第一個範例程式中, 我首先是使用了 Delegate 宣告和實體化的動作來建立委派物件, 並寫好事件處理函式:

        Public Delegate Sub myDelegate(ByVal Code As Integer)
        Dim SomeDelegate As New myDelegate(AddressOf ErrorHandler)

    宣告 Delegate 類型的這種語法確實是 .Net 裡面少見的。你不但給予 Delegate 類型的名稱 (myDelegate), 還得同時指出事件處理函式的種類 (Sub 或 Function), 以及事件處理函式所要使用的參數 (ByVal Code As Integer) 或參數陣列。而在實體化這個 Delegate 類型的時候, 其參數則變成要給予事件處理函數的位址 (AddressOf ErrorHandler)。

    還好, 我們不用去深究為什麼 Delegate 需要使用這種語法 (其實也沒什麼道理可言, 它就是這麼設計), 你只要記得它的語法如此, 知道怎麼用就可以了。

    接著, 我們就可以直接呼叫 Delegate 實體 (instance) 以發動委派 (將它當作一個方法來使用, 雖然實際上它只是個指向事件處理函式的指標而已)了:

        SomeDelegate(1)

    當然, 你必須選擇在最適當的時機去下上面這道指令 (在我們的情境中, 是發生錯誤的時候才發動), 然後事件處理函式 (ErrorHandler) 就會被呼叫。

    在第二個範例程式中, 我不使用委派, 而是使用以下語法來宣告一個事件:

        Public Event ErrorFound(ErrorCode As Integer)

    在事件 (ErrorFound) 的宣告中, 必須指定要傳入對應的事件處理函式的參數或參數陣列。

    接著, 我們使用 AddHandler 指令建立起事件和事件處理函式之間的關係, 然後再使用 RaiseEvent 指令來發動事件:

        AddHandler Me.ErrorFound, AddressOf ErrorHandler
        RaiseEvent ErrorFound(1)

    我們使用 AddHandler 指令把事件 (ErrorFound) 和事件處理函式 (ErrorHandler) 關聯在一起, 然後我們在使用 RaiseEvent 來發動事件時, 事件處理函式就會自動被呼叫。

    以上兩種做法的結果都是一樣的。當你看到這裡, 你或許會認為, 既然我們根本不需要用到委派也可以宣告跟發動事件, 那麼我們為什麼需要委派? 或許我們根本連委派是什麼都不需要知道。事實上, 只有 VB 可以這麼做。而且在 VB 中也並不是不必用到委派, 而是 VB 在背地裡幫你把委派的宣告和實體化的動作做掉了 (就是當我們在第二個範例中使用 AddHandler 指令的時候)。

    所以, 是的, 恭喜 VB 的愛用者, 你可以在不需要理會委派的情況下, 依然很方便的加入跟處理事件 (採用以上第二種做法)。當然, 你仍然可以選擇採用委派來處理事件 (採用以上第一種做法)。

    範例程式  (C#)-

    (第一種做法) -

        public delegate void myDelegate(int Code);  // 宣告委派
        public void ErrorHandler(int Code); // 建立事件處理函式
        {
            // 在這裡處理事件的因應方法
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            myDelegate someDelegate = new myDelegate(ErrorHandler); // 建立委派的實體 (Instance)
            someDelegate(); // 當有錯誤發生時直接呼叫委派實體
        }

    (第二種做法) -

        public delegate void myDelegate(int Code);
        public event myDelegate ErrorFound; // 宣告事件
        public void ErrorHandler(int Code);
        {
            // 在這裡處理事件的因應方法
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            ErrorFound = new myDelegate(ErrorHandler); // 透過委派在事件與事件處理函式間建立關聯
            ErrorFound(1); // 當有錯誤發生時發動事件
        }

    C# 在事件處理方面和 VB 略有不同。在以上兩個範例中, 結果也是完全相同的。不過, 和 VB 不一樣, 在 C# 中你沒有辦法完全棄委派於不顧, 因為 C# 並不會背地裡幫你把委派偷偷做好, 所以你仍然要自己宣告。

    在第一個範例中, 我們宣告並建立委派實體:

        public delegate void myDelegate(int Code);
        public void ErrorHandler(int Code);

    和 VB 不同, 在 C# 中我們使用 void 來表示事件處理函式是一個不帶回傳值的方法 (即 VB 的 Sub)。如果它有帶回傳值 (即 VB 的 Function), 那麼你得宣告它的回傳型別 (例如 bool 或 int 等等)。

    之後, 我們就可以對委派進行呼叫以發動它。這與 VB 的第一個範例基本上並沒有不同, 只是在 C# 中你不能在方法外面進行委派的實體化動作, 所以在範例中我們把將 Delegate 實體化的動作寫在 Page_Load() 事件函式裡面。當然, 這個動作並不一定非得寫在 Page_Load() 不可, 你只要確定你在發動事件之前確實有做過實體化的動作, 而且可以呼叫得到它就行了。

    在第一個範例中, 除了委派的實體化和 VB 的第一個範例略有不同之外, 其它地方大致上是一樣的。然而, 在第二個範例中, C# 和 VB 就有一個地方是完全不一樣的了, 請特別留意。

    在第二種方法中, 我們可以宣告事件並在必要時發動事件, 然後事件處理函式就能被自動呼叫。在這裡, 我們可以看出 C# 和 VB 有很大的差異, 主要在於事件與委派之間的依存關係, 還有, C# 在事件的宣告語法也不一樣, 請注意:

        public event myDelegate ErrorFound;

    雖然我們在這裡不直接以執行委派實體的方式發動事件, 但是你仍然必須宣告並建立一個委派實體之後, 再把事件指派給它, 然後才能發動一個事件:

        ErrorFound = new myDelegate(ErrorHandler);
        ErrorFound(1);

    看到這裡, 我希望你並沒有被搞混。不過如果你到現在還是不清楚怎麼寫這個程式的話, 那麼我建議你就直接採用第二種做法吧! 如果你寫 VB, 就採用 VB 的第二種做法; 如果你寫 C#, 就採用 C# 的第二種做法。總共就幾行程式罷了, 照著寫就行了。

    使用時機 -

    我想, 除非你只是為了應付考試, 否則我相信如果你真的對事件與委派有興趣, 一定是因為你希望能把它應用在什麼地方。坦白的說, 在 .Net 領域中, 有太多東西恐怕是你一輩子用不到的。雖然說「一招半式闖江湖」似乎是用來形容學藝不精的的人, 但由於 .Net 太龐大了, 相較之下只懂得一招半式, 卻能成功勝任手頭上工作的人卻比比皆是。

    然而, 就好像物件導向的程式設計法則雖然是 .Net 的核心, 但是你也可以完全不理會什麼物件導向的原理, 仍然可以把程式寫出來一樣; 事件和委派也是 .Net 領域中不可不熟悉的一部份, 你雖然可以對它完全不了解, 或許也還是可以勝任目前手頭上的工作, 但是你還是永遠會有面臨這個主題的時候, 那麼既然你已經看到這篇文章, 何不乾脆趁這個機會把它搞清楚呢?

    老實說, 如果你從來不寫使用者控制項 (User Controls) 或自訂控制項 (Custom Controls) 的話, 你恐怕還真的完全可以不踫事件與委派。因為不管是網頁或是視窗應用程式, 你只需要藉由既有控制項的事件處理函式 (例如 Button.Click 處理函式) 就足以應付大多數情況了。但是只要你開始寫使用者控制項或自訂控制項, 我相信你很快就會遇到如何處理自訂事件的問題。

    我在「[UserControl] 在使用者控制項中建立事件處理函式 」這篇文章裡面提出了一個簡單的解決方法 (請參考「更簡單的做法以及一個特別提醒」和其下幾篇回覆)。在這裡的情境是, 假設你寫了一個 Web 使用者控制項 ucSelectCountry.ascx 供客戶選擇國家, 然後你在網頁 A.aspx 中把這個使用者控制項加入頁面。接著, 如果使用者從使用者控制項的選單中選擇了某個國家 (例如台灣), 然後你必須立刻針對這個國家, 在網頁進行某些動作 (例如把人口、國民年所得等資訊顯示出來, 或者變更該國家的對應國碼 (例如 +886) 等等)。

    範例 -

    在上述的狀況中, 你可以在程式中加入如下的程式碼:

    VB -

        Public Event SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        Protected Sub ddl_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ddl.SelectedIndexChanged
            RaiseEvent SelectedIndexChanged(Me, e)
        End Sub

    C# -

        public event EventHandler selectedIndexChanged;
        protected void ddl_SelectedIndexChanged(...) {
           if (selectedIndexChanged != null)
              selectedIndexChanged(sender, e);
        }

    或者再更簡單一點:

        ddl.SelectedIndexChanged += new EventHandler(selectedIndexChanged); // 必須選擇在某個方法內宣告 (例如 Page_Load)

    上面這個 C# 簡捷語法, 很抱歉, 在 VB 裡面是沒有的。VB 使用者只能使用 AddHandler。

    還有, 若使用 ... += new EventHandler ... 方法來建立事件處理函式, 同樣的老問題仍然存在, 那就是你必須把這段指令找個適當的網頁事件並且放在裡面 (例如 Page_Load)。我想寫過視窗應用程式的朋友對這種寫法應該不會陌生才對, 因為在視窗應用程式中, 事件都是由系統使用這種方法幫你建立起來的。

    在這裡所使用的 EventHandler 本身是一個系統所提供的預設委派型別之一。在這種情況下, 你可以使用這個委派物件來取代自訂的委派 (換句話說, 你不必自已宣告及實體化另一個委派)。

    當你這麼做之後, 你在網頁中的該項使用者控制項就多了一個稱為 OnselectedIndexChanged 可以使用。換句話說, 你可以在 A.aspx.vb 或 A.aspx.cs 程式中撰寫對應的事件處理函式:

    .aspx -

        <%@ Register Src="../UserControls/ucSelectCountry.ascx" TagName="ucSelectCountry" TagPrefix="uc1" %>
        ...
        <uc1:ucSelectCountry ID="UcSelectCountry1" runat="server" OnselectedIndexChanged="UcSelectCountry1_SelectedIndexChanged" />

    VB -

        Protected Sub UcSelectCountry1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles UcSelectCountry1.SelectedIndexChanged
            ' 在這裡撰寫程式
        End Sub

    C# -

        protected void UcSelectCountry1_SelectedIndexChanged(object sender, EventArgs e)
        {
            // 在這裡撰寫程式
        }

    如果你不在這個使用者控制項建立一個自訂事件, 那麼我不曉得還有什麼更簡單的做法可以來處理類似的情況。當然, 你或許會認為你可以在 .ascx 程式中撰寫 DropDownList 的 SelectedIndexChanged 事件處理函式而不在 .aspx 程式裡撰寫 (若真的如此, 我也服了你)。但是 Web 使用者控制項本來就是寫來給各個不同網頁使用的, 所以如果你建立了事件, 那麼在 A.aspx 可以使用這個事件, 在 B.aspx 也可以使用這個事件... 依此類推。

    我希望我舉這個 Web 使用者控制項的例子, 有助於讓你明白事件與委派的使用時機。

    沒有上一則|日誌首頁|沒有下一則
    回應