Raising C events with an extension method - is it bad?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
In C#, events can only be raised from within the class that declares them. This is a language restriction — external code can only use += and -=. A common workaround is to create an extension method that invokes the event's underlying delegate in a thread-safe way. This pattern is not inherently bad; it simplifies the null-check-and-invoke boilerplate and is widely used in production code. However, it only works when the event is backed by a simple delegate field, and it requires passing the delegate explicitly since extension methods cannot access private members.
Standard Event Raising Pattern
The On[EventName] pattern is the standard C# convention. You copy the delegate to a local variable (or use ?.Invoke) to prevent a NullReferenceException if the last subscriber unsubscribes between the null check and the invocation.
Extension Method for Raising Events
The extension method encapsulates the null-conditional invocation pattern. The key insight is that Click.Raise(...) works because C# passes the current value of the Click delegate field as the this parameter — creating the same local copy that the manual pattern requires.
Why This Works (Thread Safety)
When the compiler evaluates Click.Raise(...), it reads the Click field once and passes that snapshot to the extension method. If another thread sets Click to null after this point, the extension method still holds the original reference. This provides the same thread safety as the manual local-variable copy.
Custom EventArgs with Extension Methods
When Extension Methods Do NOT Work
When an event uses custom add/remove accessors, the compiler does not generate a backing field with the same name. The extension method pattern only works with field-like events (the default).
Arguments For and Against
Since C# 6 introduced the ?. operator, the primary advantage of the extension method (avoiding the null check) is largely eliminated. Click?.Invoke(this, e) is one line and requires no helper method.
Common Pitfalls
- Assuming thread safety without understanding why: The extension method is thread-safe because the delegate value is copied when passed as a parameter. If you refactor to pass the event by reference or evaluate it lazily, you lose this guarantee. Understand the mechanism, not just the pattern.
- Using extension methods with custom event accessors: Events with explicit
add/removedo not have a compiler-generated backing field. Calling an extension method on the event name in that case results in a compile error — you must invoke the backing delegate directly. - Forgetting that C# 6+ has
?.Invoke(): The null-conditional operator?.achieves the same null-safe invocation in a single expression. In modern C# codebases, the extension method approach adds indirection without significant benefit. - Raising events from outside the declaring class: Extension methods do not bypass C#'s event access rules. You cannot call
someObject.Click.Raise(...)from external code — the compiler still restricts access to+=and-=only. The extension method is for use inside the class that owns the event. - Not making the raise method virtual: When using the traditional
OnEventNamepattern, marking itprotected virtualallows derived classes to override the behavior (e.g., suppress or modify events). Extension methods are static and cannot be overridden, so subclasses lose this customization point.
Summary
- Extension methods for raising events encapsulate the null-check-and-invoke pattern and are thread-safe
- They work only with field-like events (no custom
add/removeaccessors) - Since C# 6,
Click?.Invoke(this, e)achieves the same result without a helper method - The traditional
protected virtual OnEventName()pattern remains preferred when subclasses need to override event raising - Extension methods cannot be used to raise events from outside the declaring class — C# enforces this restriction regardless
Related reading
- Raising PropertyChanged in asynchronous Task and UI Thread
- Random row from Linq to Sql
- Randomize a List<T>
- Re-using HttpClient but with a different Timeout setting per request?
- Read-only list or unmodifiable list in .NET 4.0
- Read asynchronously data from NetworkStream with huge amount of packets
- Read connection string from web.config
- Read only first item from IAsyncEnumerable, then cancel

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.