C#的代理和事件

來源:互聯網
上載者:User

 1.代理 (Delegate)

        大多數情況下,當調用函數時我們會指定要直接調用的函數。比如類 MyClass 如具有一個名為 Process 的函數,我們通常會按如下方法進行調用:

MyClass myClass = new MyClass();
myClass.Process();

        這種調用在大多數情況下都是可行的。但是有些時候,我們不想直接調用函數,而希望能夠將它傳遞給其他人,讓他們進行調用。在以事件驅動的系統(形使用者介面)中,這種方法尤為有用。例如當我需要在使用者單擊某個按鈕即可執行一些代碼時,或者當我要記錄一些資訊但卻無法指定記錄方式時。

考慮以下樣本:
public class MyClass
{
        public void Process()
        {
                Console.WriteLine("Process() begin");
                // 這裡還有其他東西...
                Console.WriteLine("Process() end");
        }
}

         在此類中,我們進行一些記錄,以瞭解函數的開始時間和結束時間。但是,我們的記錄僅限於發送到控制台,這可能不是我們所需要的。我們真正需要的是能夠控制從函數外部記錄資訊的位置,同時不必使函數代碼變得複雜。

        在這種情況下,代理便是理想的解決方案。代理使我們可以指定將要調用的函數,看起來好像不需要指定哪個函數一樣。對代理的聲明類似於對函數的聲明,不同的是在這種情況下,我們所聲明的是此代理可引用的函數簽名。

我們的例子將聲明一個帶有單個字串參數且沒有傳回型別的代理。修改該類如下:

public class MyClass
{
        public delegate void LogHandler(string message);
        public void Process(LogHandler logHandler)
        {
            if (logHandler != null)
            logHandler("Process() begin");
            // 這裡還有其他東西 
            if (logHandler != null)
            logHandler ("Process() end");
        }
}

使用代理與直接調用函數相似。只是在調用函數前,我們需要檢查代理是否為空白(即不指向一個函數)。

        要調用 Process() 函數,我們需要聲明一個與代理相匹配的記錄函數,然後建立代理的執行個體,以指向該函數。然後,將此代理傳遞給 Process() 函數。
class Test
{
        static void Logger(string s)
        {
            Console.WriteLine(s);
        }

        public static void Main()
        {
            MyClass myClass = new MyClass();

            MyClass.LogHandler lh = new MyClass.LogHandler(Logger);

            myClass.Process(lh);
        }
}

Logger() 函數是一個我們要從 Process() 函數中調用的函數,我們對它進行了聲明,使其與代理相匹配。在 Main() 中,我們建立代理的一個執行個體,然後將該函數傳遞給代理建構函式,使其指向該函數。最後,我們將代理傳遞給 Process() 函數,該函數接著調用 Logger() 函數。

如果您習慣於使用 C++ 語言,您可能會認為代理很像函數指標,這種想法非常接近於事實。但是,代理並不“僅僅”是函數指標,它還提供了其它多種功能。

1.1 傳遞狀態 (Passing State)

在上面的簡單樣本中,Logger() 函數僅僅是輸出些字串。一個不同的函數可能把資訊記錄到檔案中,但是要進行這種操作,該函數需要知道把資訊寫道什麼檔案中。

對於 Win32 而言,當您傳遞函數指標時,可隨之傳遞狀態。但是對於 C#,這就沒有必要了,因為代理既可指向靜態函數,“也”可指向成員函數。以下是一個有關如何指向成員函數的樣本:

class FileLogger
{
        FileStream fileStream;
        StreamWriter streamWriter;

        public FileLogger(string filename)
        {
                fileStream = new FileStream(filename, FileMode.Create);
                streamWriter = new StreamWriter(fileStream);
        }

        public void Logger(string s)
        {
                streamWriter.WriteLine(s);
        }

        public void Close()
        {        
            streamWriter.Close();
            fileStream.Close();
        }
}

class Test
{
        public static void Main()
        {
                FileLogger fl = new FileLogger("process.log");
                MyClass myClass = new MyClass();
                MyClass.LogHandler lh = new MyClass.LogHandler(fl.Logger);
                myClass.Process(lh);
                fl.Close();
         }
}

FileLogger 類僅封裝檔案。我們修改了Main()以使代理指向 FileLogger 的 fl 執行個體的 Logger() 函數。當從 Process() 中啟用此代理時,將會調用成員函數並把字串記錄到相應的檔案中。

其優點在於,我們不必更改 Process() 函數 -對代理來說代碼都是相同的,無論引用的是靜態函數還是成員函數。

1.2 多播 (Multicasting)

雖然指向成員函數的功能已讓人感到滿意,但利用代理,您還可以巧妙地完成其它一些任務。在 C# 中,代理是“多播”的,這表示它們可同時指向一個以上的函數(即基於 System.MulticastDelegate 類型)。多播代理將維護一個函數列表。當調用該代理時,將會調用列表中的所有函數。我們可以添加第一個樣本中的記錄函數,然後調用這兩個代理。要將兩個代理組合起來,可使用 Delegate.Combine() 函數。其代碼如下:

MyClass.LogHandler lh = (MyClass.LogHandler)
Delegate.Combine(new Delegate[]
{new MyClass.LogHandler(Logger),
new MyClass.LogHandler(fl.Logger)});

啊呀,真的是很難看!幸好 C# 提供了一種更好的文法,而不用將以上文法強加給使用者。無需調用 Delegate.Combine(),僅使用 += 即可組合這兩個代理:

MyClass.LogHandler lh = null;
lh += new MyClass.LogHandler(Logger);
lh += new MyClass.LogHandler(fl.Logger);

這樣就簡潔多了。要從多播代理中刪除一個代理,可調用 Delegate.Remove() 或使用 -= 運算子(我知道自己會用哪一個)。
當你調用多播代理時,就會按出現順序對調用列表中的代理進行同步調用。如果此過程中出現了錯誤,執行過程即被中斷。
如果您想更嚴格地控制調用順序(例如要進行萬無一失的調用),則可以從代理中擷取調用列表,然後自行調用這些函數。以下是一個樣本:

foreach (LogHandler logHandler in lh.GetInvocationList())
{
        try
        {
            logHandler(message);
         }
        catch (Exception e)
        {
            // 在這裡處理異常情況嗎? 
        }
}
      代碼只是將每次調用封裝在一個 try-catch 對中,這樣在一個調用處理(handler)中引發的異常就不會妨礙對其它調用處理(handler)的啟用。

2.事件 (Events)

我們已經對代理進行了較長時間的討論,現在該談一談事件了。一個顯而易見的問題就是:“既然我們已經有了代理,為什麼還需要事件?”

回答這個問題的最好方法就是考慮使用介面物件所發生的事件。例如,一個按鈕可能有公用的“Click”代理。我們可將一個函數掛接到該代理上,這樣當單擊此按鈕時,就可以調用該代理。例如:

Button.Click = new Button.ClickHandler(ClickFunction);

它表示當單擊此按鈕時,將調用 ClickFunction()。

小測驗:上述代碼是否存在問題?我們忘記了什嗎?
答案是,我們忘記使用 += 而直接分配了代理。這表示其它任何掛接到“Button.Click”的代理現在都將解除掛接。“Button.Click”應該是公用的,以便其它對象可以對其進行訪問,因此上述情況將無法避免。同樣,要刪除代理,使用者可能會編寫以下代碼:
Button.Click = null;
這將刪除所有代理。

這些情形極其糟糕,因為在許多情況下只掛接了一個代理,問題不會明顯地表現為bug。隨後,當掛接了另一個代理時,事情就糟了!

事件在代理模型上添加了一層保護。這裡有一個支援事件的對象例子:

public class MyObject
{
    public delegate void ClickHandler(object sender, EventArgs e);
    public event ClickHandler Click;

    protected void OnClick()
    {
            if (Click != null)
            Click(this, null);
     }
}

ClickHandler 代理使用事件代理的標準模式來定義事件的簽名。它以handler的名字結尾,帶有兩個參數。第一個參數是發送此事件的對象,第二個參數用於傳遞伴隨事件發生的資訊。本例中沒有要傳遞的資訊,因此直接使用 EventArgs;但是如果有資料要傳遞,則使用從 EventArgs 派生的類(例如 MouseEventArgs)。

“Click”事件的聲明做兩件事情:首先,它聲明一個名為“Click”的代理成員變數,在類的內部使用。其次,它聲明一個名為“Click”的事件,該事件可按照常規訪問規則從類的外部進行使用(在此例中,事件為公用事件)。
一個像OnClick()這樣的函數通常包含進去以便該類型或從該類型的衍生類別型可以觸發事件。由於“Click”是代理,您將會注意到,用來觸發事件的代碼與代理的代碼相同。

與代理類似,我們使用 += 和 -= 來掛接或解除事件掛接,但與代理不同的是,僅可對事件執行這些操作。這可確保不會發生先前所討論的兩種錯誤。

使用事件是很簡單的事情。

class Test
{
        static void ClickFunction(object sender, EventArgs args)
        {
            // process the event here.
        }
        public static void Main()
        {
            MyObject myObject = new MyObject();
            myObject.Click += new MyObject.ClickHandler(ClickFunction); 
        }
}

我們建立一個與代理簽名相匹配的靜態函數或成員函數,然後用 += 向事件中添加代理的一個新執行個體。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.