C# Delegate vs Event: The Practical Difference
c# delegate vs event: Understand the practical differences between delegates and events in C#, including invocation rules, access control, and when each fits your design.
The Core Difference in Invocation Semantics
When comparing c# delegate vs event, the most important distinction is not the syntax but the contract you expose to other code. A delegate is a type that can hold a reference to one or more methods. An event is a member that uses a delegate behind the scenes but enforces stricter access rules. The event keyword adds two accessors—add and remove—that control how external code can subscribe or unsubscribe. More critically, an event can only be invoked from within the class that declares it. This prevents external code from raising the event, which is often the exact behavior you want when you are building a publisher-subscriber pattern.
Declaration Syntax and What It Implies
Consider the following declarations:
public delegate void Notify(string message); public class Publisher { public Notify OnNotify; // delegate field public event Notify NotifyEvent; // event }
The delegate field OnNotify is publicly accessible. Any code with a reference to the Publisher instance can assign a new method to it or invoke it directly. The event NotifyEvent is also public, but the compiler generates a private backing field and restricts invocation to the declaring class. External code can only use += and -= to subscribe or unsubscribe.
How Event Accessors Restrict External Invocation
The event keyword changes the accessibility of the delegate instance. Even though the event is declared public, the underlying delegate field is private. The only public operations are add and remove. This means external code cannot do:
publisher.NotifyEvent = SomeMethod; // compile-time error publisher.NotifyEvent(); // compile-time error
Instead, they must use:
publisher.NotifyEvent += SomeMethod;
This restriction is deliberate. It prevents external code from overwriting all subscribers or raising the event on behalf of the publisher. In a well-designed event-driven system, only the class that owns the event should be able to trigger it.
Multicast Behavior and Subscription Patterns
Both delegates and events are multicast by default. That means you can attach multiple methods to the same delegate or event. However, the way you manage the invocation list differs. With a delegate field, you can assign a new invocation list directly, which can accidentally remove existing subscribers. With an event, you can only add or remove one method at a time, which makes accidental overwriting impossible. This is a significant safety advantage for events in a large codebase.
Consider this example:
public class Publisher { public event Action<int> ValueChanged; public void RaiseValueChanged(int value) { ValueChanged?.Invoke(value); } }
The ?.Invoke pattern is a safe way to raise an event when there are no subscribers. It checks for null before invoking. This is a common pattern in C# event handling.
Thread Safety and Race Conditions in Subscription
A common concern with events is thread safety. The add and remove accessors are not atomic by default. In a multi-threaded environment, two threads subscribing or unsubscribing simultaneously can lead to a lost update. The .NET runtime provides a thread-safe way to add and remove event handlers if you use the add and remove accessors with a lock, but the default compiler-generated accessors use an interlocked operation to update the delegate reference. For most applications, the default behavior is sufficient, but if you are building a library that will be used in high-concurrency scenarios, you may need to implement custom accessors with a lock.
When to Use a Delegate and When to Use an Event
The decision between a delegate and an event comes down to the contract you want to expose. Use a delegate when you need to pass a method as a parameter or store a callback that the caller can invoke. For example, Func<T> and Action<T> are delegates used throughout LINQ. Use an event when you want to allow multiple subscribers and want to prevent external code from raising the event. Events are the standard choice for UI frameworks, notification systems, and any publisher-subscriber pattern where the publisher controls when notifications are sent.
Common Pitfalls and Misconceptions
One misconception is that events are slower than delegates. In practice, the performance difference is negligible because an event is just a delegate with accessor restrictions. The real difference is in API design. Another pitfall is using a delegate field where an event would be more appropriate, which can lead to external code overwriting your callback and breaking the system. Conversely, using an event where a simple delegate is needed adds unnecessary complexity. For example, a callback that should be invoked exactly once by a single consumer is better modeled as a delegate.
Practical Example: Building a Simple Notification System
Let's put the concepts together. Suppose you are building a temperature sensor class that notifies subscribers when the temperature changes. You want to expose an event so that multiple components can subscribe, but only the sensor can raise the notification.
public class TemperatureSensor { private int _temperature; public event Action<int> TemperatureChanged; public int Temperature { get => _temperature; set { if (_temperature != value) { _temperature = value; TemperatureChanged?.Invoke(_temperature); } } } }
External code can subscribe with sensor.TemperatureChanged += OnTemperatureChanged; but cannot invoke the event directly. This design keeps the publisher in control and prevents accidental misuse.
Delegate vs Event at a Glance
| Aspect | Delegate | Event |
|---|---|---|
| Invocation from outside | Allowed | Not allowed |
Assignment (=) | Allowed | Not allowed (only += and -=) |
| Multicast | Yes | Yes |
| Typical use | Callbacks, LINQ | Publisher-subscriber patterns |
| Encapsulation | Lower | Higher |