Effective C#之22:Define Outgoing Interfaces with Events

来源:互联网 发布:工商银行能办淘宝卡吗 编辑:程序博客网 时间:2024/06/05 08:20

Item 22: Define Outgoing Interfaces with Events

使用事件定义友好的接口

Events define the outgoinginterface for your type. Events are built on delegates to provide type-safefunction signatures for event handlers. Add to this the fact that most examplesthat use delegates are events, and developers start thinking that events anddelegates are the same things. In Item 21, I showed you examples of when youcan use delegates without defining events. You should raise events when yourtype must communicate with multiple clients to inform them of actions in thesystem.

事件为你的类型定义友好的接口。事件建立在委托上为事件处理者提供类型安全的方法签名。由于这点,大多数使用委托的例子都是事件,开发者开始认为事件和委托是同样的东西。在Item 21里,我向你展示了何时使用委托而不用定义事件的例子。当你的类型必须和多个用户进行交流,向它们通知系统的动作时,应该产生事件。

Consider a simple example. You'rebuilding a log class that acts as a dispatcher of all messages in an application.It will accept all messages from sources in your application and will dispatchthose messages to any interested listeners. These listeners might be attachedto the console, a database, the system log, or some other mechanism. You definethe class as follows, to raise one event whenever a message arrives:

考虑一个简单的例子。你正在构建一个log类,在一个应用程序里面用作所有消息的分发者。它将从你应用程序源里面接收所有消息,将那些消息分发给任何感兴趣的监听者。这些监听者可能和控制台、数据库、系统日志或者其它机制相绑定。你像下面这样定义类,消息到达时就产生一个事件。

 

  1.    public class LoggerEventArgs : EventArgs
  2.     {
  3.         public readonly string Message;
  4.         public readonly int Priority;
  5.         public LoggerEventArgs(int p, string m)
  6.         {
  7.             Priority = p;
  8.             Message = m;
  9.         }
  10.     }
  11.  
  12.     // Define the signature for the event handler:
  13.     public delegate void AddMessageEventHandler(object sender,
  14.       LoggerEventArgs msg);
  15.  
  16.     public class Logger
  17.     {
  18.         static Logger()
  19.         {
  20.             theOnly = new Logger();
  21.         }
  22.         private Logger()
  23.         {
  24.         }
  25.  
  26.         private static Logger theOnly = null;
  27.         public Logger Singleton
  28.         {
  29.             get {return theOnly; }
  30.         }
  31.  
  32.         // Define the event:
  33.         public event AddMessageEventHandler Log;
  34.         // add a message, and log it.
  35.         public void AddMsg(int priority, string msg)
  36.         {
  37.             // This idiom discussed below.
  38.             AddMessageEventHandler l = Log;
  39.             if (l != null)
  40.                 l(nullnew LoggerEventArgs(priority, msg));
  41.         }
  42.  }

The AddMsg method shows the properway to raise events. The temporary variable to reference the log event handleris an important safeguard against race conditions in multithreaded programs.Without the copy of the reference, clients could remove event handlers betweenthe if statement check and the execution of the event handler. By copying the reference,that can't happen.

AddMsg方法展示了产生事件的合适方式。引用log事件处理者的临时变量,在多线程程序里多个线程进行争抢的情况下,是重要的安全保障。没有这个引用的副本的话,在if检查语句和事件处理者执行之间,客户可能移除事件处理者。通过拷贝这个引用,这就不会发生了。

I've defined LoggerEventArgs tohold the priority of an event and the message. The delegate defines thesignature for the event handler. Inside the Logger class, the event fielddefines the event handler. The compiler sees the public event field definitionand creates the Add and Remove operators for you. The generated code is exactlythe same as though you had written the following:

我已经定义了LoggerEventArgs来保存事件和消息的优先权。委托为事件处理者定义了签名。在Logger类内部,事件字段定义了事件处理者。编译器看到了公共的事件字段定义,为你创建AddRemove操作符。生成的代码就和你这样子写的完全一样:

 

  1.    public class Logger
  2.     {
  3.         private AddMessageEventHandler log;
  4.  
  5.         public event AddMessageEventHandler Log
  6.         {
  7.             add
  8.             {
  9.                 log = log + value;
  10.             }
  11.             remove
  12.             {
  13.                 log = log - value;
  14.             }
  15.         }
  16.  
  17.         public void AddMsg (int priority, string msg)
  18.         {
  19.             AddMessageEventHandler l = log;
  20.             if (l != null)
  21.                 l (nullnew LoggerEventArgs (priority, msg));
  22.         }
  23.  }

The C# compiler creates the addand remove accessors for the event. I find the public event declarationlanguage more concise, easier to read and maintain, and more correct. When youcreate events in your class, declare public events and let the compiler createthe add and remove properties for you. You can and should write these handlersyourself when you have additional rules to enforce.

C#编译器为事件创建addremove访问器。我发现公共事件声明语言更简练,更易读,更易维护,更正确。当你在你的类里面创建事件时,声明公共事件,让编译器为你创建addremove属性。当你有附加规则要强加上去的话,你能并且应该自己编写这些处理者。

Events do not need to have anyknowledge about the potential listeners. The following class automaticallyroutes all messages to the Standard Error console:

事件不需要对潜在监听者有任何了解。下面的类自动将所有的消息传递给标准的错误控制台。

 

  1.    class ConsoleLogger
  2.     {
  3.         static ConsoleLogger()
  4.         {
  5.             logger.Log += new AddMessageEventHandler(Logger_Log);
  6.         }
  7.  
  8.         private static void Logger_Log(object sender,LoggerEventArgs msg)
  9.         {
  10.             Console.Error.WriteLine("{0}:/t{1}",
  11.               msg.Priority.ToString(),
  12.               msg.Message);
  13.         }
  14. }

Another class could direct outputto the system event log:

另一个类可以直接输出到系统事件日志中;

 

  1.   class EventLogger
  2.     {
  3.         private static string eventSource;
  4.         private static EventLog logDest;
  5.  
  6.         static EventLogger()
  7.         {
  8.             logger.Log += new AddMessageEventHandler(Event_Log);
  9.         }
  10.  
  11.         public static string EventSource
  12.         {
  13.             get
  14.             {
  15.                 return eventSource;
  16.             }
  17.             set
  18.             {
  19.                 eventSource = value;
  20.                 if (!EventLog.SourceExists(eventSource))
  21.                     EventLog.CreateEventSource(eventSource, "ApplicationEventLogger");
  22.  
  23.                 if (logDest != null)
  24.                     logDest.Dispose();
  25.                 logDest = new EventLog();
  26.                 logDest.Source = eventSource;
  27.             }
  28.         }
  29.  
  30.         private static void Event_Log(object sender,LoggerEventArgs msg)
  31.         {
  32.             if (logDest != null)
  33.                 logDest.WriteEntry(msg.Message,
  34.                   EventLogEntryType.Information,
  35.                   msg.Priority);
  36.         }
  37. }

Events notify any number ofinterested clients that something happened. The Logger class does not need anyprior knowledge of which objects are interested in logging events.

事件通知任意数量感兴趣的客户:有事发生了。对于哪个对象对logging事件感兴趣,Logger类不需要预先有任何了解。

The Logger class contained only oneevent. There are classes (mostly Windows controls) that have very large numbersof events. In those cases, the idea of using one field per event might beunacceptable. In some cases, only a small number of the defined events isactually used in any one application. When you encounter that situation, youcan modify the design to create the event objects only when needed at runtime.

Logger类仅仅包含一个事件,存在一些包含很多事件的类(多数是Wimdows控件)。那样的话,每个事件使用一个字段的想法可能就不能接受了。在一些情况下,仅仅有一小部分定义的事件是在任何一个应用程序里都会实际用到的。当你碰到这种情况的时候,你可以修改设计,仅仅在运行时需要的时候才创建事件对象。

The core framework containsexamples of how to do this in the Windows control subsystem. To show you how,add subsystems to the Logger class. You create an event for each subsystem.Clients register on the event that is pertinent to their subsystem.

核心框架包含了例子,展示了在Wimdows控件子系统里面该如何这样做。为了向你展示如何做,将子系统加入到Logger类里面。你为每个子系统都创建一个事件。客户注册到和子系统相关的事件上。

The extended Logger class has aSystem.ComponentModel.EventHandlerList container that stores all the eventobjects that should be raised for a given system. The updated AddMsg() methodnow takes a string parameter that specifies the subsystem generating the logmessage. If the subsystem has any listeners, the event gets raised. Also, if anevent listener has registered an interest in all messages, its event getsraised:

扩展后的Logger类有一个System.ComponentModel.EventHandlerList容器,用来存储应该在特定系统下产生的所有事件对象。更新后的AddMsg()方法现在采用了一个string参数,用来指定生成log消息的子系统。如果子系统有任何监听者,事件就会产生。同时,如果一个事件监听者在所有的消息上进行了注册,它的事件也会产生。

 

  1.    public class Logger
  2.     {
  3.         private static System.ComponentModel.EventHandlerList
  4.           Handlers = new System.ComponentModel.EventHandlerList();
  5.  
  6.         static public void AddLogger(string system, AddMessageEventHandler ev)
  7.         {
  8.             Handlers[system] = ev;
  9.         }
  10.  
  11.         static public void RemoveLogger(string system)
  12.         {
  13.             Handlers[system] = null;
  14.         }
  15.  
  16.         static public void AddMsg(string system, int priority, string msg)
  17.         {
  18.             if ((system != null) && (system.Length > 0))
  19.             {
  20.                 AddMessageEventHandler l =
  21.                   Handlers[system] as AddMessageEventHandler;
  22.  
  23.                 LoggerEventArgs args = new LoggerEventArgs(priority, msg);
  24.                 if (l != null)
  25.                     l(null, args);
  26.  
  27.                 // The empty string means receive all messages:
  28.                 l = Handlers[""as AddMessageEventHandler;
  29.                 if (l != null)
  30.                     l(null, args);
  31.             }
  32.         }
  33. }

This new example stores theindividual event handlers in the EventHandlerList collection. Client codeattaches to a specific subsystem, and a new event object is created. Subsequentrequests for the same subsystem retrieve the same event object. If you developa class that contains a large number of events in its interface, you shouldconsider using this collection of event handlers. You create event members whenclients attach to the event handler on their choice. Inside the .NET Framework,the System.Windows.Forms.Control class uses a more complicated variation ofthis implementation to hide the complexity of all its event fields. Each eventfield internally accesses a collection of objects to add and remove theparticular handlers. You can find more information that shows this idiom in theC# language specification (see Item 49).

新例子在EventHandlerList集合里面存储各自的事件处理者。客户代码和指定的子系统相关联,一个新事件对象就被创建。对同一个子系统来说,后来的请求重新获得同样的事件对象。如果你开发一个在接口上包含大量事件的类,应该考虑使用这个事件处理者的集合。当客户基于它们的选择绑定到事件处理者的时候,你就创建了事件成员。在.Net框架内部,System.Windows.Forms.Control类使用了一个这种实现的更复杂的变种来隐藏事件字段的复杂性。每个事件字段在内部访问一个对象集合来增加或者移除特定的处理者。在C#语言规范(Item49)里面,你可以找到更多的信息来展示该习惯。

You define outgoing interfaces inclasses with events: Any number of clients can attach handlers to the eventsand process them. Those clients need not be known at compile time. Events don'tneed subscribers for the system to function properly. Using events in C#decouples the sender and the possible receivers of notifications. The sendercan be developed completely independently of any receivers. Events are thestandard way to broadcast information about actions that your type has taken.

在类里面使用事件来定义更友好的接口。任意数量的客户可以将处理者绑定到事件上并进行处理。在编译时不需要知道那些客户。对于系统恰当的完成它的功能来说,事件不需要订阅。在C#里面使用事件,将发送者和可能被通知的接收者进行了解耦。发送者可以完全独立于任何接收者被开发。事件是用来广播你的类型所采取的动作信息的标准方式。

原创粉丝点击