Custom Event Arguments

Vaibhav • September 11, 2025

In the previous article, we explored how events are declared and handled in C#. You learned how to use delegates and the built-in EventHandler pattern to notify subscribers when something happens. But what if you want to pass more than just a signal? What if your event needs to carry additional information - like a progress percentage, a timestamp, or a user ID?

That’s where custom event arguments come in. In this article, we’ll explore how to create and use custom EventArgs classes to pass rich, structured data with your events. You’ll learn how to design event payloads that are clear, extensible, and easy to consume - all while following the standard patterns used throughout the .NET ecosystem.

Why Use Custom Event Arguments?

The default EventHandler delegate uses EventArgs as its second parameter. This works fine when you don’t need to pass any data - you just use EventArgs.Empty. But most real-world events need to carry context.

For example, a file download event might need to include the filename, size, and completion status. A login event might include the username and timestamp. Rather than passing multiple parameters or using global state, you can bundle this data into a custom class.

Custom event arguments are classes that inherit from System.EventArgs. This keeps your code consistent with .NET conventions and allows you to use EventHandler<TEventArgs>.

Creating a Custom EventArgs Class

To create a custom event arguments class, define a class that inherits from EventArgs and add properties for the data you want to pass. Here’s a simple example:

public class DownloadEventArgs : EventArgs
{
    public string FileName { get; }
    public int FileSize { get; }
    public bool IsSuccessful { get; }

    public DownloadEventArgs(string fileName, int fileSize, bool isSuccessful)
    {
        FileName = fileName;
        FileSize = fileSize;
        IsSuccessful = isSuccessful;
    }
}

This class includes three properties: the name of the file, its size in bytes, and whether the download was successful. The constructor initializes these properties when the event is raised.

Declaring an Event with Custom Arguments

Once you’ve defined your custom EventArgs class, you can declare an event using EventHandler<TEventArgs>. This ensures that your event follows the standard two-parameter signature: sender and event data.

public class Downloader
{
    public event EventHandler<DownloadEventArgs> DownloadCompleted;

    public void SimulateDownload()
    {
        // Simulate download logic...
        var args = new DownloadEventArgs("report.pdf", 2048, true);
        DownloadCompleted?.Invoke(this, args);
    }
}

The DownloadCompleted event is raised with an instance of DownloadEventArgs. Subscribers can access all the relevant data through the args parameter.

Handling Events with Custom Arguments

To handle an event with custom arguments, you write a method that matches the EventHandler<TEventArgs> signature. This means two parameters: object sender and your custom EventArgs type.

void OnDownloadCompleted(object sender, DownloadEventArgs e)
{
    Console.WriteLine($"File: {e.FileName}, Size: {e.FileSize} bytes, Success: {e.IsSuccessful}");
}

You can then subscribe to the event using +=:

Downloader downloader = new Downloader();
downloader.DownloadCompleted += OnDownloadCompleted;
downloader.SimulateDownload();

When the event is raised, your handler receives the data and prints it. This pattern keeps your event handling clean and focused.

Using Lambdas with Custom EventArgs

You can also use lambda expressions to handle events - even when using custom event arguments. This is useful for short, inline logic that doesn’t need a separate method.

downloader.DownloadCompleted += (sender, e) =>
{
    Console.WriteLine($"Downloaded {e.FileName} ({e.FileSize} bytes)");
};

The lambda matches the expected signature and accesses the event data through e. This keeps your code concise and readable - especially in small applications or test scenarios.

Designing Good EventArgs Classes

When designing custom event arguments, follow these guidelines:

Use read-only properties to ensure that event data is immutable once created. This prevents accidental changes and makes your events more predictable.

Include only the data that’s relevant to the event. Don’t overload your EventArgs with unrelated properties - keep it focused and purposeful.

Use clear, descriptive property names. Subscribers should be able to understand what each property means without reading documentation.

Design your EventArgs classes like data contracts - small, focused, and immutable. This makes your events easier to consume and less error-prone.

Multiple Events with Different Arguments

A class can expose multiple events - each with its own custom EventArgs type. This allows you to separate concerns and provide tailored data for each event.

public class FileProcessor
{
    public event EventHandler<FileOpenedEventArgs> FileOpened;
    public event EventHandler<FileClosedEventArgs> FileClosed;

    public void OpenFile(string path)
    {
        FileOpened?.Invoke(this, new FileOpenedEventArgs(path));
    }

    public void CloseFile(string path, TimeSpan duration)
    {
        FileClosed?.Invoke(this, new FileClosedEventArgs(path, duration));
    }
}

Each event uses a different EventArgs class - allowing subscribers to receive only the data they need. This improves clarity and reduces coupling.

Extending EventArgs for Future Needs

If you anticipate that your event might need more data in the future, design your EventArgs class to be extensible. You can add optional properties or use inheritance to create specialized versions.

For example, you might start with a base class:

public class BaseEventArgs : EventArgs
{
    public DateTime Timestamp { get; }

    public BaseEventArgs()
    {
        Timestamp = DateTime.UtcNow;
    }
}

Then extend it for specific events:

public class ErrorEventArgs : BaseEventArgs
{
    public string ErrorMessage { get; }

    public ErrorEventArgs(string message)
    {
        ErrorMessage = message;
    }
}

This pattern allows you to reuse common properties while customizing each event’s payload.

Common Mistakes with Custom EventArgs

Custom event arguments are simple - but they can be misused. Here are some pitfalls to avoid:

Don’t use object or dynamic types for event data. This breaks type safety and makes your code harder to understand.

Don’t expose mutable properties unless absolutely necessary. Event data should be read-only to prevent side effects.

Don’t pass unrelated data just because it’s convenient. Keep your event arguments focused and meaningful.

Note: If your event needs to pass multiple unrelated values, consider whether it should be split into multiple events - each with its own purpose and payload.

Summary

Custom event arguments allow you to pass rich, structured data with your events. By creating classes that inherit from EventArgs, you can provide context, improve readability, and follow .NET conventions. You learned how to design EventArgs classes, declare events with EventHandler<T>, and handle events using both named methods and lambdas.

Custom event arguments are a key part of building robust, event-driven systems. They make your events more expressive, your handlers more focused, and your code easier to maintain. In the next article, we’ll explore Event Best Practices - covering naming conventions, memory management, and design principles for scalable event architectures.