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.