VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Objective-C编程 >
  • Effective Csharp选则22项用事件定义实现对外接口

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  可以用事件给你的类型定义一些外部接口。事件是基于委托的,因为委托可以提供类型安全的函数签名到事件句柄上。加上大多数委托的例子都是使用事件来说明的,以至于开发人员一开始都认为委托与事件是一回事。在原则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#中事件可以减弱消息的发送者和可能的消息接受者之间的关系,发送者可以设计成与接受者无关。事件是类型把动作信息发布出去的标准方法。

 

 

  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 );
 }
}



相关教程