Page Content

Tutorials

Events In C#: An Essential Concept for Modern Applications

Events in C#

When something noteworthy happens, such a user action or a change in state, one object (the publisher) can utilise C#’s events mechanism to alert other objects (the subscribers). Many applications, particularly those with graphical user interfaces, rely on this “notification pattern” essential to their operation.

Here’s a thorough breakdown of what happens in C#:

Core Concepts of Events

Notification Pattern: A class can use events to convey a message that a specific action has occurred or will occur. This could be a keystroke, mouse click, or page load event.

Publisher and Subscriber: A publisher or event sender is the entity that “raises” or “sends” an event. Subscribing classes are those that “receive” these notifications. Multiple subscribers may be assigned to a single event, and a subscriber may manage several events from various publishers.

Dependency on Delegates: Events are enclosed delegates in C#. Subscribers are required to implement the event handler method’s signature (return type and parameters), which is defined by a delegate. In contrast to Java, which handles events via specific listener classes, C# makes use of delegates.

No Return Type: A void event is one that has no return type.

Declaring an Event

Using the event keyword and a delegate type, events are declared inside a class or struct.

Syntax for declaring an event: Modifier event delegateType eventVariable

  • Modifier: Can be public, protected, private, internal, static, virtual, sealed, override, or abstract.
  • event: The term that denotes an event declaration.
  • delegateType: The type of delegate that defines the signature of the event handler is represented by this.
  • eventVariable: The event’s identifier for its name.

Standard Event Delegates: Microsoft offers two pre-installed delegates to make event management easier:

  • public delegate void EventHandler(object sender, EventArgs e);
  • public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);. The base class for event data is called EventArgs, from which custom event arguments are derived to convey extra data.

Example of declaring a standard event:

public class Server
{
    // defines the event using the standard EventHandler delegate
    public event EventHandler DataChangeEvent;
}

Event Properties: By putting event delegates in a data structure such as EventHandlerList rather than one field per delegate, event properties help classes that raise a lot of events save storage space.

Raising an Event

An event can be raised only by the declaring type (the publisher). Invoking the relevant delegate is how you raise an event.

Example of raising an event (C# 6.0 and later):

using System;
public class MyClass
{
    // Declaring an event using the EventHandler delegate
    public event EventHandler MyEvent;
    /// <summary>
    /// Raises the MyEvent event.
    /// This method is typically public and acts as the trigger for the event.
    /// </summary>
    public void RaiseEvent()
    {
        // Call the protected virtual method to raise the event.
        // This provides a point for derived classes to override event raising logic.
        OnMyEvent(EventArgs.Empty);
    }
    /// <summary>
    /// Protected virtual method to raise the MyEvent event.
    /// This is the conventional pattern for raising events in .NET.
    /// It allows derived classes to customize or prevent event firing.
    /// </summary>
    /// <param name="e">An EventArgs that contains the event data.</param>
    protected virtual void OnMyEvent(EventArgs e)
    {
        // Using the null-conditional operator (?.) for thread-safe invocation.
        // This ensures that the event is only invoked if there are subscribers.
        // It's a concise way to check for null and invoke in a thread-safe manner.
        MyEvent?.Invoke(this, e);
    }
}
// Example Usage (optional, for demonstration)
public class Program
{
    public static void Main(string[] args)
    {
        MyClass myInstance = new MyClass();
        // Subscribe to the event
        myInstance.MyEvent += MyEventHandler;
        // Raise the event
        Console.WriteLine("Raising event for the first time...");
        myInstance.RaiseEvent();
        // Unsubscribe from the event (optional)
        myInstance.MyEvent -= MyEventHandler;
        // Raise the event again (no output if unsubscribed)
        Console.WriteLine("\nRaising event for the second time (after unsubscribing)...");
        myInstance.RaiseEvent();
        Console.ReadLine();
    }
    // Event handler method
    private static void MyEventHandler(object sender, EventArgs e)
    {
        Console.WriteLine("MyEvent was triggered!");
        Console.WriteLine($"Sender Type: {sender.GetType().Name}");
    }
}

Output

Raising event for the first time...
MyEvent was triggered!
Sender Type: MyClass
Raising event for the second time (after unsubscribing)...

Prior to invocation, it was a good idea for C# versions before 6.0 to assign the event to a temporary variable in order to guarantee thread safety and prevent NullReferenceException in the event that a subscriber unsubscribes just before the event is raised.

Subscribing to an Event

Subscribers sign up for an event so they can be informed when it is raised. The addition assignment operator (+=) is usually used for this.

Methods for subscribing:

Named Event Handlers: It assigns a method to the event that matches the delegate’s signature.

Anonymous Methods and Lambda Expressions: Lambda expressions or inline anonymous methods, which are helpful for handling short event logic, can be used to subscribe.

For example.smtpClient.SendCompleted += (sender, args) => Console.WriteLine("Email sent");..

Multiple Controls to Single Handler: You can bind an event process to more than one control. Which control triggered the event is determined by the sender argument in the event handler.

Unsubscribing from an Event

You can use the subtraction assignment operator (-=) to cease getting event alerts. The publisher maintains a reference to the subscriber’s delegate, which keeps it from being garbage collected, hence it is essential to unsubscribe from events before discarding a subscriber object to avoid memory leaks.

Example of unsubscribing:

server.DataChangeEvent -= server_DataChanged;

Unsubscribing directly is challenging if an anonymous method was used for subscription, unless the anonymous method was saved in a delegate variable.

Event Handler Method Signature

The parameters and return type of the subscriber class’s event handler method must match those of the delegate type specified for the event.

Example of a common event handler signature: public void HandlerName(object sender, EventArgsT args)

sender: what caused the incident to occur.

args: event-specific data in an instance of EventArgs (or a related class).

Custom Event Arguments ()

It is possible to construct a custom class that derives from System.EventArgs when an event needs to communicate particular information to its subscribers. It is customary for this class’s name to conclude with EventArgs.

Example of PriceChangingEventArgs for a Product class:

using System;
// Custom EventArgs class to carry data for a "price changing" event.
// It inherits from EventArgs, which is the base class for all event data.
public class PriceChangingEventArgs : EventArgs
{
    // Constructor to initialize the event data.
    // It takes the current and new price values as arguments.
    public PriceChangingEventArgs(int currentPrice, int newPrice)
    {
        // Using C# property auto-initialization in the constructor.
        // This is equivalent to 'this.CurrentPrice = currentPrice;' and 'this.NewPrice = newPrice;'.
        // It's a common, concise way to assign values to auto-implemented properties.
        CurrentPrice = currentPrice;
        NewPrice = newPrice;
    }
    // Property to hold the price before the change.
    // 'get;' makes it readable.
    // 'private set;' ensures it can only be set within this class (typically in the constructor),
    // making it read-only to external consumers of the event.
    public int CurrentPrice { get; private set; }
    // Property to hold the proposed new price.
    // 'get;' makes it readable.
    // 'set;' allows external event subscribers to modify the 'NewPrice' before the change
    // is finalized, which is typical for "changing" (before-action) events.
    public int NewPrice { get; set; }
}
// Example of how this might be used in a class that raises the event
public class Product
{
    private int _price;
    public int Price
    {
        get { return _price; }
        set
        {
            // Only raise the event if the price is actually changing
            if (_price != value)
            {
                // Create custom event arguments
                PriceChangingEventArgs args = new PriceChangingEventArgs(_price, value);
                // Raise the event, allowing subscribers to modify NewPrice or cancel
                OnPriceChanging(args);
                // Update the price only if it wasn't cancelled (if you added a Cancel property to args)
                // For this example, we just set it to the potentially modified NewPrice
                _price = args.NewPrice;
            }
        }
    }
    // Declare the event using the custom EventArgs
    public event EventHandler<PriceChangingEventArgs> PriceChanging;
    // Protected virtual method to raise the event
    protected virtual void OnPriceChanging(PriceChangingEventArgs e)
    {
        // Thread-safe invocation using the null-conditional operator
        PriceChanging?.Invoke(this, e);
    }
    public Product(int initialPrice)
    {
        _price = initialPrice;
    }
}
// Example of how to subscribe to and handle the event
public class Program
{
    public static void Main(string[] args)
    {
        Product laptop = new Product(1000);
        // Subscribe to the event
        laptop.PriceChanging += OnLaptopPriceChanging;
        Console.WriteLine($"Initial Laptop Price: {laptop.Price}");
        Console.WriteLine("Attempting to set price to 1200...");
        laptop.Price = 1200; // This will trigger the event
        Console.WriteLine($"Final Laptop Price: {laptop.Price}"); // Will be 1100 due to handler modification
        Console.WriteLine("\nAttempting to set price to 900...");
        laptop.Price = 900; // This will trigger the event again
        Console.WriteLine($"Final Laptop Price: {laptop.Price}"); // Will be 950 due to handler modification
        Console.ReadLine();
    }
    // Event handler method
    private static void OnLaptopPriceChanging(object sender, PriceChangingEventArgs e)
    {
        Console.WriteLine($"  Price is changing from {e.CurrentPrice} to proposed {e.NewPrice}.");
        // Example: If the new price is too high, cap it.
        if (e.NewPrice > 1100)
        {
            Console.WriteLine("  Proposed price is too high! Capping at 1100.");
            e.NewPrice = 1100; // Modify the NewPrice property
        }
        // Example: If the new price is too low, set a minimum.
        else if (e.NewPrice < 950)
        {
            Console.WriteLine("  Proposed price is too low! Setting to 950.");
            e.NewPrice = 950; // Modify the NewPrice property
        }
    }
}

Output

Initial Laptop Price: 1000
Attempting to set price to 1200...
  Price is changing from 1000 to proposed 1200.
  Proposed price is too high! Capping at 1100.
Final Laptop Price: 1100
Attempting to set price to 900...
  Price is changing from 1100 to proposed 900.
  Proposed price is too low! Setting to 950.
Final Laptop Price: 950

Additionally, you may construct cancelable events by deriving from CancelEventArgs and adding a Cancel property to your custom event arguments. This will enable subscribers to stop the action from happening.

Common Event Scenarios and Examples

GUI Interactions: In Windows Forms (and other UI frameworks), events are frequently used to record user activities such as key pushes (KeyPress, KeyDown, KeyUp), mouse movements (MouseMove, MouseClick, MouseEnter, MouseLeave, MouseDown, MouseUp, MouseHover), and button clicks (Click event).

Timer: System timers.The Elapsed event (or Tick event in System.Windows.Forms.Timer) is used by the Timer class to indicate the end of a predetermined interval.

Property Changes: To inform clients that a property value has changed, the INotifyPropertyChanged interface employs an event (PropertyChanged), which is essential for data binding in user interface frameworks. When raising this event, you can supply the property name using the nameof operator.

File System Monitoring: FileSystemWatcher alerts users to changes in a designated directory using events such as Changed, Created, Deleted, and Renamed.

Like a well-planned dance, events are set up by the publisher, who also announces the style of dance (the delegate signature) and the commencement of the music (raising the event). Interested in that specific dance, the subscribers sign up to take part. As the music starts, each registered dancer follows their steps (execute their event handlers), adding to the total performance according to the given beat (event data). Dancers unregister if they leave the floor so they don’t stay after the performance.

You can also read Delegates In C#: Enabling Flexible and Extensible Code

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index