Delegates and Events

Delegates and Events

What are delegates?

  • A delegate is a type that holds a reference to a method
  • Delegate provides a late binding mechanism where the caller can supply a method that will be part of the execution
  • Delegates are multi cast because they can hold reference to more than 1 method
  • Multi-cast delegates are methods chained together
  • Use Action for delegates that do not return a value
  • Use Function for delegates that return a value
  • Use Predicate for delegates that return a test on a single value

In its simplest form

using System;

public static class Logger
{
    public static Action<string> WriteMessage;
    public static void LogMessage(string msg)
    {
        WriteMessage(msg);
    }
}

class MainClass {
  public static void Main (string[] args) {
    //Lambda Form
    Logger.WriteMessage += (msg) => Console.WriteLine(msg);
    
    //Non Lambda
    Logger.WriteMessage += WriteMessage2;
    
    Logger.LogMessage("Hello World");
  }
 
  public static void WriteMessage2(string msg)
  {
    Console.WriteLine(msg + " #2");
  }
}

Events vs Delegates


  Event Delegate
Listeners Optional Required
Return Type void any
Lifetime Longer - till program is active Shorter - till method is called

The Event Pattern

  • An event is a message sent by an object to signal the occurrence of an action.
  • All events in .NET are based on the EventHandler delegate. Use EventHandler for events that do not require event data. Use EventHandler otherwise.
  • Event names are verbs that are either in present or past tense
  • Event Data classes end in EventArgs and they no longer (but used to) require to inherit from the EventArgs class (.NET Core)
  • Events with async subscribers must wrap the await code in a try-catch block to capture errors. Without it the calling method would have continued already by nature of async and you’re left with no way to log or handle errors gracefully.

Subscribing to Events

void HandleCustomEvent(object sender, CustomEventArgs args)  
{  
   // Do something useful here.  
}  

// These are all the same
publisher.RaiseCustomEvent += HandleCustomEvent;  // C# 2.0
publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent); // C# 1.0
publisher.RaiseCustomEvent += (sender, args) => { //Method Body Here }

Full Example of Publishing, Raising and Subscribing to Events

namespace DotNetEvents
{
    using System;
    using System.Collections.Generic;

    // Define a class to hold custom event info
    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string s)
        {
            message = s;
        }
        private string message;

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
    }

    // Class that publishes an event
    class Publisher
    {

        // Declare the event using EventHandler<T>
        public event EventHandler<CustomEventArgs> RaiseCustomEvent;

        public void DoSomething()
        {
            // Write some code that does something useful here
            // then raise the event. You can also raise an event
            // before you execute a block of code.
            OnRaiseCustomEvent(new CustomEventArgs("Did something"));

        }

        // Wrap event invocations inside a protected virtual method
        // to allow derived classes to override the event invocation behavior
        protected virtual void OnRaiseCustomEventClassic(CustomEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<CustomEventArgs> handler = RaiseCustomEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Format the string to send inside the CustomEventArgs parameter
                e.Message += String.Format(" at {0}", DateTime.Now.ToString());

                // Use the () operator to raise the event.
                handler(this, e);
            }
        }
        
        //C# 6 Approach
        protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
        {
          e.Message += String.Format(" at {0}", DateTime.Now.ToString());
          RaiseCustomEvent?.Invoke(this, e);
        }
    }

    //Class that subscribes to an event
    class Subscriber
    {
        private string id;
        public Subscriber(string ID, Publisher pub)
        {
            id = ID;
            // Subscribe to the event using C# 2.0 syntax
            pub.RaiseCustomEvent += HandleCustomEvent;
        }

        // Define what actions to take when the event is raised.
        void HandleCustomEvent(object sender, CustomEventArgs e)
        {
            Console.WriteLine(id + " received this message: {0}", e.Message);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Publisher pub = new Publisher();
            Subscriber sub1 = new Subscriber("sub1", pub);
            Subscriber sub2 = new Subscriber("sub2", pub);

            // Call the method that raises the event.
            pub.DoSomething();

            // Keep the console window open
            Console.WriteLine("Press Enter to close this window.");
            Console.ReadLine();

        }
    }
}

References