Objective C # Principle 22: Define external interfaces with events
Item 22: Define outgoing interfaces with events
You can use events to define some external interfaces for your type. The event is based on the delegate, because the delegate can provide type-safe function signature to the event handle. In addition, most examples of delegation are described by events, so that developers initially think that delegation and events are the same thing. In principle 21, I have shown some examples of using delegation without events. When your type communicates with multiple other customers, you must trigger events to complete their actions.
In a simple example, you are creating a log class, which is applied in the same way as an information publisher.ProgramTo publish all messages. It accepts all messages distributed from the program source and publishes these messages to interested audiences. These listeners can be consoles, databases, system logs, or other mechanisms. You can define a class like the following to trigger an event when a message arrives:
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 ));
}
}
The addmsg method demonstrates an appropriate method to trigger an event. The temporary log handle variable is very important. It ensures that the log handle is secure in various multithreading situations. Without this referenced copy, you may remove the event handle between the if detection statement and the officially executed event handle. With the reference of copy, this will not happen.
I also defined a loggereventargs to store the priority of events and messages. The delegate defines the signature of the event handle. In the logger class, the event field defines the event handle. The compiler considers the event as a public field and adds the add and remove operations for you. GeneratedCodeThe handwriting is the same as yours:
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 # The Compiler creates the add and remove operations to access events. Have you seen that the common event Definition Language is concise, easy to read and maintain, and more accurate. When you add an event to a class, you allow the compiler to create, add, and remove attributes. You can, and you should, manually write these handles when you want to add them in principle.
The event does not have to know any information that may become a listener. The following class automatically sends all messages to the standard error device (console:
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 );
}
}
Another class can be directly output to System Event Logs:
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 );
}
}
When an event occurs, it notifies any number of customers interested in the message. The logger class does not need to know any objects interested in messages in advance.
The logger class contains only one event. Most Windows controls have many events. In this case, adding a field to each event is not an acceptable method. In some cases, only a few events are defined in a program. In this case, you can modify the design and create it only when an event is required during running.
The author's obvious Lovesickness is that when he wants to say something well, he will never, or seldom talk about the negative effects of this matter. In fact, events have a great impact on performance and should be used as little as possible. The event brings us many benefits, but we should not abuse it. The author does not explain the negative effects of the event here .)
The extended logger class has a system. componentmodel. eventhandlerlist container that stores the event objects that should be triggered in a given system. The updated addmsg () method now has a parameter that can indicate in detail the messages of subsystem logs. If the subsystem has any listener, the event is triggered. Similarly, if the event listener listens on all messages of interest, it will also be triggered:
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 );
}
}
}
In this new example, some event handles are stored in the event handlerlist set. The customer code is added to a special subsystem and a new event object is created. Then, when the same subsystem needs to retrieve the same event object. If you develop a class that contains a large number of event instances, you should consider using the event handle set. When a customer attaches an event handle, you can choose to create an event member. Within the. NET Framework, the system. Windows. Forms. control class uses a complex and variable implementation for events, thus hiding complex event member fields. Each event field internally adds and removes the actual handle through an access set. You can find more information about C # in Principle 49.
You use events to define an external interface on the class: any number of customers can add handles to events and process them. These objects do not need to know who they are during compilation. The event system can use them properly without having to know the details. In C #, events can weaken the relationship between message senders and possible message recipients. The sender can be designed to be unrelated to the receiver. Events are the standard method for releasing action information.
==========================================
Item 22: Define outgoing interfaces with events
Events define the outgoing interface for your type. events are built on delegates to provide type-safe function signatures for event handlers. add to this the fact that most examples that use delegates are events, and developers start thinking that events and delegates are the same things. in item 21, I showed you examples of when you can use delegates without defining events. you shocould raise events when your type must communicate with multiple clients to inform them of actions in the system.
Consider a simple example. you're building 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 dispatch those messages to any interested listeners. these listeners might be attached to the console, a database, the system log, or some other mechanic. you define the class as follows, to raise one event whenever a message arrives:
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 ));
}
}
The addmsg method showsthe proper way to raise events. the temporary variable to reference the log event handler is an important safeguard against race conditions in multithreaded programs. without the copy of the reference, clients cocould remove Event Handlers between the if statement check and the execution of the event handler. by copying the reference, that can't happen.
I 've defined loggereventargs to hold the priority of an event and the message. the delegate defines the signature for the event handler. inside the logger class, the event field defines the event handler. the compiler sees the public event field definition and creates the add and remove operators for you. the generated code is exactly the same as though you had written the following:
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 ));
}
}
}
The C # compiler creates the add and remove accessors for the event. I find the public event declaration language more concise, easier to read and maintain, and more correct. when you create events in your class, declare public events and let the compiler create the add and remove properties for you. you can and shocould write these handlers yourself when you have additional rules to enforce.
Events do not need to have any knowledge about the potential listeners. The following class automatically routes all messages to the standard error console:
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 );
}
}
Another class cocould direct output to the System Event Log:
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 );
}
}
Events permission y any number of interested clients that something happened. The logger class does not need any prior knowledge of which objects are interested in logging events.
The logger class contained only one event. there are classes (mostly Windows controls) that have very large numbers of events. in those cases, the idea of using one field per event might be unacceptable. in some cases, only a small number of the defined events is actually used in any one application. when you encounter that situation, you can modify the design to create the event objects only when needed at runtime.
The core framework contains examples 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.
The extended logger class has a system. componentmodel. eventhandlerlist container that stores all the event objects that shoshould be raised for a given system. the updated addmsg () method now takes a string parameter that specifies the subsystem generating the log message. if the subsystem has any listeners, the event gets raised. also, if an event listener has registered an interest in all messages, its event gets raised:
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 );
}
}
}
This new example stores the individual event handlers in the event handlerlist collection. client Code attaches to a specific subsystem, and a new event object is created. subsequent requests for the same subsystem retrieve the same event object. if you develop a class that contains a large number of events in its interface, you shoshould consider using this collection of event handlers. you create event members when clients attach to the event handler on their choice. inside. net Framework, the system. windows. forms. control class uses a more complicated variation of this implementation to hide the complexity of all its event fields. each event field internally accesses a collection of objects to add and remove the participating handlers. you can find more information that shows this idiom in the C # language specification (see item 49 ).
You define outgoing interfaces in classes with events: any number of clients can attach handlers to the events and process them. those clients need not be known at compile time. events don't need subscribers for the system to function properly. using events in C # decouples the sender and the possible receivers of communications. the sender can be developed completely independently of any receivers. events are the standard way to broadcast information about actions that your type has taken.