Effective C# 使用时间定义外发接口

事件为类型定义了外发接口(outgoing interface)。C#的事件建立在委托之上,委托为事件处理器(event handler)提供了类型安全的函数签名。由于绝大多数使用委托的例子都是事件,一些开发人员便开始认为事件和委托是同一种东西。在条款21中,我向大家展示了“可以使用委托,但却没有定义事件”的例子。当我们的类型必须和多个客户通信,并为它们提供系统中的行为通知时,我们才应该触发事件。

考虑一个简单的例子。我们正在创建一个日志类,来派发应用程序中的所有消息。它会从应用程序中的消息源接受所有消息,然后将它们派发给感兴趣的侦听者。这些侦听者可能会被连接到控制台、数据库、系统日志或者其他一些机制上。我们可以像下面这样定义这个类,当有消息到达时便触发一个事件:

 

Effective C# 使用时间定义外发接口 public   class  LoggerEventArgs : EventArgs
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
public readonly string Message;
Effective C# 使用时间定义外发接口
public readonly int Priority;
Effective C# 使用时间定义外发接口
public LoggerEventArgs ( int p, string m )
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口    Priority 
= p;
Effective C# 使用时间定义外发接口    Message 
= m;
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
//  定义事件处理器的签名:
Effective C# 使用时间定义外发接口
public   delegate   void  AddMessageEventHandler(  object  sender,
Effective C# 使用时间定义外发接口LoggerEventArgs msg );
Effective C# 使用时间定义外发接口
public   class  Logger
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
static Logger( )
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口    _theOnly 
= new Logger( );
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
private Logger( )
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
private static Logger _theOnly = null;
Effective C# 使用时间定义外发接口
public Logger Singleton
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口    
get
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口      
return _theOnly;
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
// 定义事件:
Effective C# 使用时间定义外发接口
public event AddMessageEventHandler Log;
Effective C# 使用时间定义外发接口
// 添加一个消息,并做日志记录。
Effective C# 使用时间定义外发接口
public void AddMsg ( int priority, string msg )
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口    
// 下面将讨论这个惯常做法。
Effective C# 使用时间定义外发接口
  AddMessageEventHandler l = Log;
Effective C# 使用时间定义外发接口    
if ( l != null )
Effective C# 使用时间定义外发接口      l ( 
nullnew LoggerEventArgs( priority, msg ) );
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口

 

 

AddMsg ()方法展示了触发事件的正确方法。其中,引用Log事件处理器的临时变量是一个重要的安全措施,它可以应对多线程环境中的竞争条件。如果没有这个引用的副本,客户代码可能会在if判断语句和事件处理器执行之间删除事件处理器。通过对引用的副本,这种事情就不会发生了。

LoggerEventArgs 类中定义了事件的优先级和消息内容。AddMessageEventHandler委托则为事件处理器定义了签名。在Logger类内部,事件字段Log 定义了事件处理器。编译器看到这个公有的事件字段定义后,会为我们创建对应的Add和Remove操作符。编译器产生的代码就好像我们编写了如下的代码一样:

 

Effective C# 使用时间定义外发接口 public   class  Logger
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
private AddMessageEventHandler _Log;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
public event AddMessageEventHandler Log
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    add
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      _Log 
= _Log + value;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    remove
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      _Log 
= _Log - value;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
public void AddMsg (int priority, string msg)
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      AddMessageEventHandler l 
= _Log;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
if (l != null)
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        l (
nullnew LoggerEventArgs (priority, msg));
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}
Effective C# 使用时间定义外发接口

 

 

C#编译器会为我们定义的事件创建add和 remove访问器。我认为公有的事件声明更简练,更易于阅读和维护,也更正确。当我们在自己的类中创建事件时,应该声明公有事件,然后让编译器为我们创建add和remove属性。只有在需要添加额外的规则时,我们才应该自己编写这些处理代码。

事件并不需要知道潜在的侦听者。下面的类会自动将所有消息发送给标准错误控制台:

 

Effective C# 使用时间定义外发接口 class  ConsoleLogger
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
static ConsoleLogger()
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    logger.Log 
+= new AddMessageEventHandler( Logger_Log );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
private static void Logger_Log( object sender,
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    LoggerEventArgs msg )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    Console.Error.WriteLine( 
"{0}:\t{1}",
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      msg.Priority.ToString(),
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      msg.Message );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口

 

 

下面的类则可以将消息输出到系统事件日志中:

 

 

Effective C# 使用时间定义外发接口 class  EventLogger
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
private static string eventSource;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
private static EventLog logDest;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
static EventLogger()
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    logger.Log 
+=new AddMessageEventHandler( Event_Log );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
public static string EventSource
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
get
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
return eventSource;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
set
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      eventSource 
= value;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
if ( ! EventLog.SourceExists( eventSource ) )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        EventLog.CreateEventSource( eventSource,
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口          
"ApplicationEventLogger" );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
if ( logDest != null )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        logDest.Dispose( );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      logDest 
= new EventLog( );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      logDest.Source 
= eventSource;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
private static void Event_Log( object sender,
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    LoggerEventArgs msg )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
if ( logDest != null )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      logDest.WriteEntry( msg.Message,
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        EventLogEntryType.Information,
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        msg.Priority );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口

 

 

事件发生时,它会通知所有感兴趣的客户对象。因此,Logger类不需要预先知道哪些对象对日志事件感兴趣。

Logger 类只包含了一个事件。但也有一些类(绝大多数是Windows控件)包含的事件数量非常多。这时候,为每个事件都定义一个字段的做法是不可接受的。在某些情况下,只有很少的事件会在程序中真正发挥作用。当遇到这种情况时,我们可以改变设计,根据运行时的需要动态创建事件对象。

.NET框架内核在Windows控件子系统中包含有这方面的做法示例。为了演示其中的做法,这里会向Logger类添加一些子系统。我们可以为每个子系统创建一个事件。客户则会登记和它们的子系统相关的事件。

扩展后的Logger类包含一个 System.ComponentModel.EventHandlerList容器,该容器中存储着所有的事件对象,这些事件将会针对具体的子系统来被触发。更新后的AddMsg()方法现在接受一个字符串参数,用于指定产生日志消息的子系统。如果子系统有侦听者,那么事件就会被触发。另外,如果一个事件侦听者登记了所有的消息,它的事件也会被触发:

 

Effective C# 使用时间定义外发接口 public   class  Logger
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
private static System.ComponentModel.EventHandlerList
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    Handlers 
= new System.ComponentModel.EventHandlerList();
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
static public void AddLogger(
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
string system, AddMessageEventHandler ev )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    Handlers[ system ] 
= ev;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
static public void RemoveLogger( string system )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    Handlers[ system ] 
= null;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
static public void AddMsg ( string system,
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
int priority, string msg )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
if ( ( system != null ) && ( system.Length > 0 ) )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    
{
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      AddMessageEventHandler l 
=
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        Handlers[ system ] 
as AddMessageEventHandler;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      LoggerEventArgs args 
= new LoggerEventArgs(
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        priority, msg );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
if ( l != null )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        l ( 
null, args );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
// 空字符串意味着接受所有消息:
Effective C# 使用时间定义外发接口

Effective C# 使用时间定义外发接口      l 
= Handlers[ "" ] as AddMessageEventHandler;
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口      
if ( l != null )
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口        l( 
null, args );
Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口    }

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口
Effective C# 使用时间定义外发接口}

Effective C# 使用时间定义外发接口

 

上面的代码会在EventHandlerList 集合中存储各个事件处理器。当客户代码关联到一个特定的子系统上时,新的事件对象就会被创建。对同一个子系统的后续请求会获取相同的事件对象。如果我们的类在其接口中包含有大量的事件,我们就应该考虑使用这样的事件处理器集合。当客户代码真正关联有事件处理器时,我们才会创建事件成员。在.NET框架内部,System.Windows.Forms.Control类使用了一种更复杂的实现,来隐藏所有事件字段操作的复杂性。每一个事件字段在内部会通过访问一个对象集合,来添加和删除特定的处理器。大家可以在C#语言规范(参见条款49)中找到有关这种常见做法的更多信息。

综上所述,我们使用事件来定义类型中的外发接口,任意数量的客户对象都可以将自己的处理器登记到事件上,然后处理它们。这些客户对象不需要在编译时存在。事件也不必非要有订阅者才能正常工作。在C#中使用事件可以对发送者和可能的通知接受者进行解耦。发送者可以完全独立于接收者进行开发。事件是一种广播类型行为信息的标准方式。

 

你可能感兴趣的:(effective)