User Tools

Site Tools


notes:csharp:events

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
notes:csharp:events [2017/01/10]
admin
notes:csharp:events [2020/10/08] (current)
leszek
Line 1: Line 1:
 ====== Events in C# ====== ====== Events in C# ======
  
-(MSDN) Events enable an object to notify other objects when something of interest occurs. The class that sends (or //raises//) the event is called the //publisher// and the classes that receive (or //handle//) the event are called //subscribers//.+(MSDN) Events enable an object to notify other objects when something of interest occurs. The class that sends (or //raises//) the event is called the //publisher// and the classes that receive (or //handle//) the event are called //subscribers// (a.k.a. //observers//).
  
 Events have the following properties: Events have the following properties:
Line 9: Line 9:
   * You may not assign an event handler to an event using '=', you have to use '+='   * You may not assign an event handler to an event using '=', you have to use '+='
   * Event handlers should be declared private or protected, not public.   * Event handlers should be declared private or protected, not public.
 +
 +You should raise events when your type must communicate with multiple subscribers to inform them of actions.
  
 Although events can be based on any delegate type it is recommended that you base your events on the .NET pattern by using //EventHandler// or //EventHandler<TEventArgs>// delegates. Although events can be based on any delegate type it is recommended that you base your events on the .NET pattern by using //EventHandler// or //EventHandler<TEventArgs>// delegates.
Line 44: Line 46:
     class VideoClip     class VideoClip
     {     {
-        // [2] Declare the event using EventHandler<T>.+        // [2] Declare the public event using EventHandler<T>.
         public event EventHandler<RatingChangedArgs> RatingChanged;         public event EventHandler<RatingChangedArgs> RatingChanged;
  
-        // [4] Wrap event invocations inside a protected virtual method +        // [3] Wrap the event invocation code inside a protected virtual  
-        // to allow derived classes to override the event invocation behavior. +        // method to allow derived classes to override the invocation behavior. 
-        protected virtual void OnRatingChanged(RatingChangedArgs args) +        protected virtual void OnRatingChanged(RatingChangedArgs args) => 
-        {+            // The ?. operator ensures that the event is raised only when  
 +            // any listeners are attached to the event.
             RatingChanged?.Invoke(this, args);             RatingChanged?.Invoke(this, args);
-        } 
  
         public string Title { get; set; }         public string Title { get; set; }
Line 69: Line 71:
                     return;                     return;
  
-                // [5] Inform subscribers when video rating has changed.+                // [4] Inform subscribers when video rating has changed.
                 OnRatingChanged(new RatingChangedArgs(rating, value));                 OnRatingChanged(new RatingChangedArgs(rating, value));
                 rating = value;                 rating = value;
Line 84: Line 86:
             this.id = id;             this.id = id;
  
-            // [6] Subscribe to the event.+            // [5] Subscribe to the event.
             clip.RatingChanged += Subscriber_RatingChanged;             clip.RatingChanged += Subscriber_RatingChanged;
         }         }
  
-        // [7] Define what actions to take when the event is raised.+        // [6] Define what actions to take when the event is raised.
         private void Subscriber_RatingChanged(object sender, RatingChangedArgs args)         private void Subscriber_RatingChanged(object sender, RatingChangedArgs args)
         {         {
Line 98: Line 100:
         }         }
     }     }
 +    
     class Program     class Program
     {     {
Line 109: Line 111:
             Subscriber s2 = new Subscriber("s2", clip);             Subscriber s2 = new Subscriber("s2", clip);
  
-            // [8] Raise the RatingChanged event.+            // [7] Raise the RatingChanged event.
             clip.Rating = 4.9f; // change the rating             clip.Rating = 4.9f; // change the rating
         }         }
Line 129: Line 131:
 </code> </code>
  
-In the above code the following two lines are equivalent:+In the above codethe following two code snippets are equivalent:
 <code csharp> <code csharp>
-// [6] Subscribe to the event.+// [5] Subscribe to the event.
 clip.RatingChanged += Subscriber_RatingChanged; // C# 2.0 clip.RatingChanged += Subscriber_RatingChanged; // C# 2.0
 </code> </code>
  
 <code csharp> <code csharp>
-// [6] Subscribe to the event.+// [5] Subscribe to the event.
 clip.RatingChanged += new EventHandler<RatingChangedArgs>(Subscriber_RatingChanged); // C# 1.1 clip.RatingChanged += new EventHandler<RatingChangedArgs>(Subscriber_RatingChanged); // C# 1.1
 </code> </code>
  
  
-Also, these two code snippets are equivalent:+Also, the following two code snippets are equivalent:
 <code csharp> <code csharp>
 RatingChanged?.Invoke(this, args); // C# 6 (null conditional operator) RatingChanged?.Invoke(this, args); // C# 6 (null conditional operator)
Line 243: Line 245:
 { {
     Console.WriteLine("Number of exceptions: {0}", exc.InnerExceptions.Count);     Console.WriteLine("Number of exceptions: {0}", exc.InnerExceptions.Count);
 +}
 +</code>
 +
 +====EventHandlerList====
 +
 +Some classes (such as Windows controls) have large numbers of events. If only a small number of the events is actually used in an application, you can create the event objects only when they are needed at runtime.
 +
 +Use an object of type ''%%System.ComponentModel.EventHandlerList%%'' as a container for event objects. The ''%%EventHandlerList%%'' has the following methods:
 +
 +  * ''%%AddHandler(Object, Delegate)%%'' - adds a delegate to the list.
 +  * ''%%RemoveHandler(Object, Delegate)%%'' - removes a delegate from the list.
 +
 +Example: A Logger class collecting events from given subsystems [Source: "Effective C#" by Bill Wagner]:
 +<code csharp>
 +using System.ComponentModel.EventHandlerList;
 +...
 +public sealed class Logger
 +{
 +    private static EventHandlerList _handlers = new EventHandlerList();
 +    
 +    // The system parameter represents a subsystem that generated a message.
 +    public static void AddLogger(string system, EventHandler<LoggerEventArgs> ev) =>
 +        _handlers.AddHandler(sender, ev);
 +        
 +    public static void RemoveLogger(string system, EventHandler<LoggerEventArgs> ev) =>
 +        _handlers.RemoveHandler(sender, ev);
 +        
 +    public static void AddMessage(string system, int priority, string message)
 +    {
 +        if (!string.IsNullOrEmpty(system))
 +        {
 +            var handler = _handlers[system] as EventHandler<LoggerEventArgs>;
 +            var args = new LoggerEventArgs(priority, message);
 +            
 +            // Raise an event if the given subsystem has any listeners.
 +            handler?.Invoke(null, args);
 +        }
 +    } 
 +}
 +</code>
 +
 +A Logger class that uses a generic ''%%Dictionary%%'' instead of the ''%%EventHandlerList%%'' class [Source: "Effective C#" by Bill Wagner]:
 +<code csharp>
 +public sealed class Logger
 +{
 +    private static Dictionary<string, EventHandler<LoggerEventArgs>> _handlers = 
 +        new Dictionary<string, EventHandler<LoggerEventArgs>>();
 +    
 +    public static void AddLogger(string system, EventHandler<LoggerEventArgs> ev)
 +    {
 +        if (_handlers.ContainsKey(system))
 +            _handlers[system] += ev;
 +        else
 +            _handlers.Add(system, ev);
 +    }
 +    
 +    // RemoveLogger throws an exception if the collection does not contain a handler for the given system.
 +    public static void RemoveLogger(string system, EventHandler<LoggerEventArgs> ev) =>
 +        _handlers[system] -= ev;
 +        
 +    public static void AddMessage(string system, int priority, string message)
 +    {
 +        if (!string.IsNullOrEmpty(system))
 +        {
 +            _handlers.TryGetValue(system, out var handler);
 +            var args = new LoggerEventArgs(priority, message);
 +                        
 +            // Raise an event if the given subsystem has any listeners.
 +            handler?.Invoke(null, args);
 +        }
 +    } 
 } }
 </code> </code>
Line 254: Line 327:
 While an event handler is subscribed, the publisher of the event holds a reference to the subscriber via the event handler delegate (assuming the delegate is an instance method). If the publisher lives longer than the subscriber, then it will keep the subscriber alive even when there are no other references to the subscriber. If you unsubscribe from the event with an equal handler that will remove the handler and the possible leak. While an event handler is subscribed, the publisher of the event holds a reference to the subscriber via the event handler delegate (assuming the delegate is an instance method). If the publisher lives longer than the subscriber, then it will keep the subscriber alive even when there are no other references to the subscriber. If you unsubscribe from the event with an equal handler that will remove the handler and the possible leak.
  
 +====Links====
  
 +  * MSDN: [[https://docs.microsoft.com/en-us/dotnet/standard/events/how-to-raise-and-consume-events|How to: Raise and Consume Events]]
    
  
  
notes/csharp/events.1484085158.txt.gz · Last modified: 2020/08/26 (external edit)