回覆列表
  • 1 # 菠蘿小仙女南巷花念伊

    程式碼:

    publiceventEventHandler

    C#編譯器編譯這行程式碼時,會把它翻譯成以下3個構造:

    //1.一個被初始化為null的私有委託欄位

    privateEventHandler

    //2.一個允許物件訂閱事件的公共方法add_Xxx(其中Xxx是事件的名稱)

    [MethodImpl(MethodImplOptions.Synchronized)]

    publicvoidadd_NewMail(EventHandler

    NewMail=(EventHandler

    Delegate.Combine(NewMail,value);

    }

    //3.一個允許物件登出事件的公共方法remove_Xxx(其中Xxx是事件的名稱)

    [MethodImpl(MethodImplOptions.Synchronized)]

    publicvoidremove_NewMail(EventHandler

    NewMail=(EventHandler

    Delegate.Remove(NewMail,value);

    }

    第一個構造只是一個適當的委託型別的欄位。這個欄位引用的是一個委託連結串列的首部,當事件發生時,連結串列中的委託物件將被通知。該欄位被初始化為null,這意味著剛開始沒有監聽者訂閱這個事件。當有方法訂閱事件時,該欄位會指向一個EventHandler

    注意,委託欄位,即本例中的NewMail,儘管在原始碼中將事件定義為public,但委託欄位也總是為private。將委託欄位定義為私有方式可以防止類定義外的程式碼錯誤地操作該欄位。如果該欄位為公共欄位,那麼任何程式碼都可以改變欄位的值,從而刪除所有已訂閱事件的委託例項。

    C#編譯器產生的第二個構造是一個允許其他物件訂閱事件的方法。C#編譯器透過在事件名稱(NewMail)前新增add_自動地命名方法。C#編譯器還自動地為方法產生程式碼。產生的程式碼通常呼叫System.Delegate的靜態Combine方法,並將委託例項新增到委託連結串列上,然後返回新的連結串列首部(得到已儲存到欄位中的內容)。

    當C#編譯器看到使用-=運算子登出事件委託的程式碼時,會生成一個對事件的remove方法的呼叫。

    mm.remove_NewMail(newEventHandler

    和+=運算子一樣,即使使用的程式語言不直接支援事件,我們仍然可以透過顯式地呼叫remove訪問器方法來登出事件。remove方法透過掃描連結串列,尋找傳入的密封了相同方法的委託來登出事件的委託。如果找到匹配的委託,就將其從事件的委託連結串列上移除。如果沒有找到,也不會出現任何錯誤,事件的委託連結串列也不會有任何改變。

    順便說一下,C#要求程式碼使用+=和-=運算子在連結串列上新增和移除委託。如果我們試圖顯式地呼叫add和remove方法,那麼C#編譯器將生成一個錯誤訊息:CS0571:“不能顯式呼叫運算子或訪問器方法”。

    C#編譯器為事件的add和remove方法增加[MethodImpl(MethodImplOptions.Synchronized)]屬性。這個屬性的目的是為了確保在操作例項的事件成員時,對於任何一個物件,在同一時刻只能有一個add方法或者remove方法可以執行。該屬性同樣確保在操作靜態事件成員時,同一時刻也只能有一個add方法或者remove方法可以執行。這裡需要執行緒同步,以免委託物件的連結串列被破壞。但應注意,CLR實現執行緒同步的方式中存在許多問題。

    對例項(非靜態)方法應用MethodImpl屬性時,CLR使用物件本身作為執行緒同步鎖。這意味著如果類定義了許多事件,那麼所有的add和remove方法都將使用相同的鎖,這種情況下,如果有多個執行緒同時訂閱和登出不同的事件,則會損害可擴充套件性。但這種情況非常少見,而且對於大多數應用程式,這並不是一個問題。但是,執行緒同步的指導方針中規定方法不應在物件本身上加同步鎖,因為同步鎖將對所有的程式碼公開。這意味著任何人都可以編寫程式碼鎖住這個物件,從而可能導致其他執行緒死鎖。如果希望自己編寫的型別防禦措施好,更加健壯,那麼應使用不同的物件來完成加鎖功能。10.5節將示範如何實現這個

    功能。

    在靜態方法上應用[MethodImpl(MethodImplOptions.Synchronized)]屬性時,CLR使用型別物件作為執行緒同步鎖。這又意味著如果類定義了許多靜態事件,那麼所有的add和remove方法都將使用相同的鎖,這種情況下,如果有多個執行緒同時訂閱和登出不同的事件,則會損害程式碼的效能。但是這種情況也非常少見。

    但還有一個嚴重的問題:執行緒同步指導方針指出,方法永遠不要在型別物件上加鎖,因為這個鎖將對所有的程式碼公開。另外,當型別載入與域無關時,CLR中還有一個錯誤。這種情況會導致同步鎖被使用該型別的所有應用程式域共享,如此一來,一個程式域中的程式碼會影響另一個應用程式域中執行的程式碼。實際中,C#編譯器在實現靜態add和remove方法的執行緒安全時應採用完全不同的方法。10.5節將討論一種修正C#編譯器這一缺陷的機制。

    C#和CLR允許定義一個有一個或者多個例項(非靜態)事件成員的值型別(結構)。但必須意識到,在上述情況下,C#編譯器根本不會保證執行緒安全。因為拆箱(unboxing)的值型別沒有與其相關聯的鎖物件。實際上,C#編譯器不為add和remove方法生成[MethodImpl(MethodImplOptions.Synchronized)]屬性,因為該屬性對值型別的例項方法沒有效果。遺憾的是,當例項事件定義為值型別的成員時,的確沒有更好的方式為它們保證執行緒安全。因此,建議大家儘量避免這樣做。注意,在值型別中定義的靜態事件(加上前面討論的限制)可以保證執行緒安全,因為靜態事件對型別物件(引用型別)本身加鎖。但是,如果要使程式碼健壯,應採用10.5節討論的機制。

    有時我們會感到編譯器生成的add和remove方法不是那麼理想。例如10.4節中討論的使用MicrosoftC#編譯器時遇到的所有執行緒安全問題。實際上,Microsoft的C#編譯器在安全程式設計(defensivecoding)和健壯性方面永遠不是最安全的。為了建立一個堅固的元件,建議經常採用本節介紹的技術,該技術可以用於解決與執行緒安全相關的所有問題。而且該技術同樣也可以應用於其他目的。例如,顯式實現add和remove方法的普遍原因就是型別定義了許多事件,而且又需要高效地進行儲存。有關詳情請參見10.6節。

    幸虧C#編譯器以及其他許多編譯器都允許開發人員顯式地實現add和remove訪問器方法。為了保證MailManager物件上事件的訂閱和登出的執行緒安全,我們修改了MailManager類的程式碼,修改後的程式碼如下所示:

    internalclassMailManager{

    //建立一個作為執行緒同步鎖的私有例項欄位

    privatereadonlyObjectm_eventLock=newObject();

    //增加一個引用委託連結串列頭部的私有欄位

    privateEventHandler

    //為類增加一個事件成員

    publiceventEventHandler

    //顯式實現"add"方法

    add{

    //加私有鎖,並向委託連結串列增加一個處理程式(以"value"為引數)

    lock(m_eventLock){m_NewMail+=value;}

    }

    //顯式實現"remove"方法

    remove{

    //加私有鎖,並從委託連結串列從中移除處理程式(以"value"為引數)

    lock(m_eventLock){m_NewMail-=value;}

    }

    }

  • 中秋節和大豐收的關聯?
  • 在家如何製作滑肉或者肉羹?