程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則22:用事件定義對外接口

Effective C#原則22:用事件定義對外接口

編輯:關於C#

可以用事件給你的類型定義一些外部接口。事件是基於委托的,因為委托可 以提供類型安全的函數簽名到事件句柄上。加上大多數委托的例子都是使用事件 來說明的,以至於開發人員一開始都認為委托與事件是一回事。在原則21裡,我 已經展示了一些不在事件上使用委托的例子。在你的類型與其它多個客戶進行通 信時,為了完成它們的行為,你必須引發事件。

一個簡單的例子,你正 在做一個日志類,就像一個信息發布機一樣在應用程序裡發布所有的消息。它接 受所有從程序源發布的消息,並且把這些消息發布到感興趣的聽眾那裡。這些聽 眾可以是控制台,數據庫,系統日志,或者是其它的機制。就可以定義一個像下 面這樣的類,當消息到達時來引發事件:

public class LoggerEventArgs : EventArgs
{
 public readonly string Message;
 public readonly int Priority;
 public LoggerEventArgs ( int p, string m )
 {
  Priority = p;
  Message = m;
 }
}
// Define the signature for the event handler:
public delegate void AddMessageEventHandler( object sender,
 LoggerEventArgs msg );
public class Logger
{
 static Logger( )
 {
  _theOnly = new Logger( );
 }
 private Logger( )
 {
 }
  private static Logger _theOnly = null;
 public Logger Singleton
 {
  get
  {
   return _theOnly;
  }
 }
 // Define the event:
  public event AddMessageEventHandler Log;
 // add a message, and log it.
 public void AddMsg ( int priority, string msg )
  {
  // This idiom discussed below.
   AddMessageEventHandler l = Log;
  if ( l != null )
   l ( null, new LoggerEventArgs( priority, msg ) );
 }
}

AddMsg方法演示了一個恰當的方法來引發事件。臨時的日志句柄 變量 是很重要的,它可以確保在各種多線程的情況下,日志句柄也是安全的。 如果沒有這個引用的COPY,用戶就有可能在if檢測語句和正式執行事件句柄之間 移除事件句柄。有了引用COPY,這樣的事情就不會發生了。

我還定義了 一個LoggerEventArgs來保存事件和消息的優先級。委托定義了事件句柄的簽名 。而在Logger類的內部,事件字段定義了事件的句柄。編譯器會認為事件是公共 的字段,而且會為你添加Add和Remove兩個操作。生成的代碼與你這樣手寫的是 一樣的:

public class Logger
{
 private AddMessageEventHandler _Log;
 public event AddMessageEventHandler Log
 {
  add
  {
    _Log = _Log + value;
  }
  remove
  {
    _Log = _Log - value;
  }
 }
  public void AddMsg (int priority, string msg)
  {
    AddMessageEventHandler l = _Log;
   if (l != null)
     l (null, new LoggerEventArgs (priority, msg));
  }
 }
}

C#編譯器創建Add和Remove操作來訪問事件。看到了嗎, 公共的事件定義語言很簡潔,易於閱讀和維護,而且更准確。當你在類中添加一 個事件時,你就讓編譯器可以創建添加和移除屬性。你可以,而且也應該,在有 原則要強制添加時自己手動的寫這些句柄。

事件不必知道可能成為監聽 者的任何資料,下面這個類自動把所有的消息發送到標准的錯誤設備(控制台)上 :

class ConsoleLogger
{
 static ConsoleLogger ()
 {
  logger.Log += new AddMessageEventHandler( Logger_Log );
 }
 private static void Logger_Log( object sender,
  LoggerEventArgs msg )
 {
   Console.Error.WriteLine( "{0}:\t{1}",
    msg.Priority.ToString(),
   msg.Message );
 }
}

另一個類可以直接輸出到系統事件日志:

class EventLogger
{
 private static string eventSource;
  private static EventLog logDest;
 static EventLogger()
  {
  logger.Log +=new AddMessageEventHandler( Event_Log );
 }
 public static string EventSource
 {
  get
  {
   return eventSource;
  }
  set
   {
   eventSource = value;
   if ( ! EventLog.SourceExists( eventSource ) )
     EventLog.CreateEventSource( eventSource,
      "ApplicationEventLogger" );
   if ( logDest != null )
    logDest.Dispose( );
   logDest = new EventLog( );
   logDest.Source = eventSource;
  }
 }
 private static void Event_Log( object sender,
   LoggerEventArgs msg )
 {
  if ( logDest != null )
    logDest.WriteEntry( msg.Message,
     EventLogEntryType.Information,
    msg.Priority );
 }
}

事件會在發生一些事情時,通知任意多個對消息感興趣 的客戶。Logger類不必預先知道任何對消息感興趣的對象。

Logger類只 包含一個事件。大多數windows控件有很多事件,在這種情況下,為每一個事件 添加一個字段並不是一個可以接受的方法。在某些情況下,一個程序中只實際上 只定義了少量的事件。當你遇到這種情況時,你可以修改設計,只有在運行時須 要事件時在創建它。

(譯注:作者的一個明顯相思就是,當他想說什麼好 時,就決不會,或者很少說這個事情的負面影響。其實事件對性能的影響是很大 的,應該盡量少用。事件給我們帶來的好處是很多的,但不要海濫用事件。作者 在這裡沒有明說事件的負面影響。)

擴展的Logger類有一個 System.ComponentModel.EventHandlerList容器,它存儲了在給定系統中應該引 發的事件對象。更新的AddMsg()方法現在帶一個參數,它可以詳細的指示子系統 日志的消息。如果子系統有任何的監聽者,事件就被引發。同樣,如果事件的監 聽者在所有感興趣的消息上監聽,它同樣會被引發:

public class Logger
{
 private static System.ComponentModel.EventHandlerList
  Handlers = new System.ComponentModel.EventHandlerList();
 static public void AddLogger(
  string system, AddMessageEventHandler ev )
  {
  Handlers[ system ] = ev;
 }
 static public void RemoveLogger( string system )
 {
  Handlers[ system ] = null;
 }
 static public void AddMsg ( string system,
  int priority, string msg )
 {
  if ( ( system != null ) && ( system.Length > 0 ) )
  {
    AddMessageEventHandler l =
    Handlers[ system ] as AddMessageEventHandler;
   LoggerEventArgs args = new LoggerEventArgs(
    priority, msg );
   if ( l != null )
    l ( null, args );
   // The empty string means receive all messages:
   l = Handlers[ "" ] as AddMessageEventHandler;
   if ( l != null )
    l( null, args );
  }
 }
}

這個新的例子在 Event HandlerList集合中存儲了個別的事件句柄,客戶代碼添加到特殊的子系 統中,而且新的事件對象被創建。然後同樣的子系統需要時,取回同樣的事件對 象。如果你開發一個類包含有大量的事件實例,你應該考慮使用事件句柄集合。 當客戶附加事件句柄時,你可以選擇創建事件成員。在.Net框架內部, System.Windows.Forms.Control類對事件使用了一個復雜且變向的實現,從而隱 藏了復雜的事件成員字段。每一個事件字段在內部是通過訪問集合來添加和移除 實際的句柄。關於C#語言的這一特殊習慣,你可以在原則49中發現更多的信息。

你用事件在類上定義了一個外接的接口:任意數量的客戶可以添加句柄 到事件上,而且處理它們。這些對象在編譯時不必知道是誰。事件系統也不必知 道詳細就可以合理的使用它們。在C#中事件可以減弱消息的發送者和可能的消息 接受者之間的關系,發送者可以設計成與接受者無關。事件是類型把動作信息發 布出去的標准方法。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved