2013年7月26日 星期五

C# 中定義 Event 的標準流程


1. 實作一個  System.EventArgs 的子代, 強烈建議設計成 immutable. C# 建議名字最好叫做 XxxEventArgs, 例如:

public class NewMailEventArgs : EventArgs { ...; }

2. 於發送端 class 定義一個 event member 如下 (不一定要 public), 根據命名規範, 應該要大寫開頭

public class MailManager {
    public event EventHandler<NewMailEventArgs> NewMail;
}

3. 於發送端 class 定義一個 protected virtual method 如下:

public class MailManager {
    protected virtual void OnNewMail(NewMailEventArgs e) {
        EventHandler<NewMailEventArgs> temp = this.NewMail;
        if(temp != null) temp(this, e);
    }
    ...
}

4. 當事件發生時, 呼叫上述的 protected virtual method

-------------------------------------------------------------------------
重點:
步驟 3 之所以要切成兩半, 是為了防止多緒時的 race condition. 因為 delegate 是 immutable, 故只需如步驟 3 所做就可以達成防止 race condition 的需求.

不過步驟 3 有一個問題: 若 compiler 夠聰明, 它可能會將步驟 3 優化如下:
    protected virtual void OnNewMail(NewMailEventArgs e) {
        if(this.newMail != null) this.NewMail(this, e);
    }

CLR 為了保證回溯相容性, 故不會做這種優化. 但為了防止未來版本的 CLR 做此優化, 寫 .NET 4.0 以上的 C# 程式時可以把步驟 3 改成這樣:

public class MailManager {
    protected virtual void OnNewMail(NewMailEventArgs e) {
        EventHandler<NewMailEventArgs> temp = Volatile.Read(ref this.NewMail);
        if(temp != null) temp(this, e);
    }
    ...
}

-------------------------------------------------------------------------
步驟三其實是可以被公式化的, 以下是透過 class extension 來對步驟 3 做公式化的方法:

public static class EventArgExtensions {
    public static void Raise<T>(this T e, Object sender, ref EventHandler<T> eventDelegate) {
        EventHandler<T> temp = Volatile.Read(ref eventDelegate);
        if(temp != null) temp(sender, e);
    }
}

於是原來的步驟三可以寫成這樣:
protected virtual void OnNewMail(NewMailEventArgs e) {
    e.Raise(this, ref this.NewMail);
}

-------------------------------------------------------------------------
EventHandler 其實是一個 delegate, 宣告如下:
public delegate void EventHandler<T>(Object sender, T e);

-------------------------------------------------------------------------
C# Compiler 其實會把下面這宣告

public class MailManager {
    public event EventHandler<NewMailEventArgs> NewMail;
}

偷偷的改寫為這樣:

public class MailManager {
    private EventHandler<NewMailEventArgs> NewMail = null;
    public void add_NewMail(EventHandler<NewMailEventArgs> value) {
        ...; // 將 value 以 thread-safe 的方式加入 this.NewMail
    }
    public voie remove_NewMail(EventHandler<NewMailEventArgs> value){
        ...; // 將 value 以 thread-safe 的方式自 this.NewMail 中移除
    }
}

注意!!! add_XXX 以及 remove_XXX 的宣告修飾詞會與當初 event 的修飾詞一樣. 如果當初的 event 是 virtual 則 compiler 產生出來的這兩個 methods 就會是 virtual. 其他如 protected, private static 亦然.

-------------------------------------------------------------------------
以下是一個事件接收端的案例:
public class Fax {
    public Fax(MailManager mm)  {
        mm.NewMail += this.FaxMsg;
    }
    public void Unregister(MailManager mm) {
        mm.NewMail -= this.FaxMsg;
    }
    private void FoxMsg(Object sender, NewMailEventArgs e) {
        ...; // 處理之
    }
}

上面的 mm.NewMail += this.FaxMsg; 其實會被 compiler 改為:
mm.add_NewMail(new EventHandler<NewMailEventArgs>(this.FaxMsg));

mm.NewMail -= this.FaxMsg; 亦會被 compiler 改為:
mm.remove_NewMail(new EventHandler<NewMailEventArgs>(FaxMsg));



出處: CLR via C# 4Ed Chapter 11

沒有留言:

張貼留言