Back to Blog
C#

C# Event vs Delegate: Key Differences

c# event vs delegate: Understand the technical difference between C# events and delegates, including invocation rules, assignment restrictions, and when to use each.

C#EventsDelegatesMulticastDesign Patterns
Diagram comparing C# event and delegate access and invocation rules

Comparing c# event vs delegate often comes down to one rule: an event is a delegate that the declaring class controls. The compiler transforms an event into a private delegate field with public add and remove accessors. That single restriction changes how you can assign and invoke the delegate, which is why events and delegates are not interchangeable in every scenario.

What a Delegate Is and How to Declare It

A delegate is a type that holds a reference to a method with a specific signature. You declare a delegate type, then create an instance that points to a method, and invoke it through the delegate. Delegates are multicast, meaning they can reference multiple methods in a single invocation list.

public delegate void Notify(string message); public class Logger { public void LogToConsole(string message) => Console.WriteLine($"Console: {message}"); public void LogToFile(string message) => File.AppendAllText("log.txt", $"{message}\n"); } // Usage var logger = new Logger(); Notify notify = logger.LogToConsole; notify += logger.LogToFile; notify("Application started");

Here Notify is a delegate type. The variable notify holds a reference to two methods. When you invoke notify, both methods run in the order they were added. This works because delegates support multicast through the + operator.

What an Event Is and How It Differs from a Delegate

An event is a member that uses a delegate type underneath, but it exposes only add and remove accessors to external code. The declaring class can invoke the event directly, but outside code can only subscribe or unsubscribe. This prevents external code from reassigning the entire invocation list or invoking the event on behalf of the class.

public class Button { public event EventHandler? Click; public void SimulateClick() { Click?.Invoke(this, EventArgs.Empty); } } // Usage var button = new Button(); button.Click += (sender, e) => Console.WriteLine("Clicked"); button.SimulateClick();

In this example, Click is an event. The SimulateClick method invokes it. External code can attach a handler with += or remove it with -=, but it cannot do button.Click = null or button.Click.Invoke(...). That is the fundamental difference from a public delegate field.

Invocation and Assignment Rules: The Core Difference

The compiler enforces the difference at the language level. If you declare a public delegate field, external code can:

  • Assign a new method reference with =.
  • Invoke the delegate directly.
  • Replace the entire invocation list.

With an event, external code can only:

  • Add a handler with +=.
  • Remove a handler with -=.

This restriction is not a runtime feature; it is a compile-time rule. The event member is compiled into a private delegate field and two public methods, add_EventName and remove_EventName. The C# compiler enforces that external code uses only those accessors.

OperationDelegate fieldEvent
Assign with =AllowedNot allowed
Invoke directlyAllowedNot allowed (outside class)
Add handler with +=AllowedAllowed
Remove handler with -=AllowedAllowed

This table summarizes the practical difference. The event pattern is designed to protect the publisher's ability to control when the event fires.

When to Use a Delegate vs an Event

Use a delegate when you need a general-purpose callback that can be passed around and invoked by the receiver. For example, a sorting function that accepts a comparison delegate gives the caller control over ordering. The delegate is an argument to a method, not a member that the class exposes.

Use an event when you are implementing a publisher-subscriber pattern. The class that declares the event is the publisher. It decides when to raise the event, and it should be the only code that invokes it. Subscribers attach handlers but cannot trigger the event themselves.

A common guideline is to use an event when the invocation should be controlled by the declaring class, and a delegate when the receiver needs to invoke the callback. If you expose a delegate as a public member, you lose the ability to prevent external code from clearing or invoking it, which can lead to unexpected behavior.

Common Mistakes with Events and Delegates

One frequent mistake is exposing a public delegate field instead of an event. This allows any code to invoke the delegate, breaking encapsulation. For example, a UI component that exposes a public Action field lets external code trigger the action without going through the component's own logic.

Another mistake is using an event when the subscriber needs to return a value or cancel the operation. Events are designed for notifications, not for requesting data. If you need a callback that returns a value, a delegate is more appropriate. You can still use a multicast delegate, but you must handle multiple return values manually.

A third mistake is forgetting to check for null before invoking an event. The ?.Invoke pattern is safe and concise, but it only works if the event is not null. In multithreaded scenarios, you should copy the event to a local variable before checking and invoking to avoid a race condition where the last subscriber unsubscribes between the check and the invocation.

public class SafePublisher { public event EventHandler? DataReady; public void RaiseDataReady() { EventHandler? handler = DataReady; handler?.Invoke(this, EventArgs.Empty); } }

This local copy ensures that the invocation list is consistent even if another thread modifies the event during the call.

Performance and Overhead Considerations

Events and delegates have minimal performance overhead. The main cost is the delegate invocation itself, which is a virtual call through a function pointer. Multicast delegates maintain an invocation list, so adding or removing handlers requires allocating a new delegate instance if the list has more than one entry. This is a minor allocation, but it can matter in high-frequency subscription changes.

Events add a small layer of indirection because the compiler generates add and remove methods. These methods perform a Combine or Remove operation on the underlying delegate field. In practice, the overhead is negligible compared to the work done by the handlers themselves.

If you are designing a hot path where delegates are invoked millions of times, consider whether you need multicast at all. A single-cast delegate is faster because it does not need to iterate an invocation list. You can use a plain delegate and assign a single method, but you lose the ability to have multiple subscribers.

Choosing the Right Abstraction for Your Design

The decision between an event and a delegate depends on who is allowed to invoke the callback and who owns the invocation lifecycle. Use an event when the declaring class must be the sole trigger. Use a delegate when the receiver is expected to call it, such as a callback parameter in a method.

Consider the following concrete scenarios:

  • UI event handlers: Use events. The button raises Click, and external code subscribes. The button controls when the event fires.
  • Custom sorting: Use a delegate. Array.Sort takes a Comparison<T> delegate that the sort algorithm invokes internally.
  • Async completion callback: Use a delegate. The async method accepts an Action or Func that it invokes when the operation completes. The caller does not need to subscribe or unsubscribe.
  • Domain events: Use events. A domain object raises an event to notify other parts of the system, but it should not let external code trigger it.

A good rule of thumb is to ask: "Should external code be able to invoke this directly?" If the answer is no, use an event. If yes, a delegate is appropriate. This rule keeps your public API clear and prevents accidental invocation from outside the class.

Events and delegates are both built on the same underlying mechanism, but the language-level restrictions give them different roles. Choosing the right one is not about performance or syntax; it is about encapsulation and control over who can trigger the callback.

c# event vs delegate: Practical Usage and Code Examples | RYUSLOG DEV