In the ever-evolving landscape of modern software development, C# has emerged as a language that blends the best of object-oriented and functional paradigms. For developers building responsive applications, scalable services, or elegant APIs, understanding the trio of delegates, events, and lambda expressions is not just useful—it’s essential – Lambdas in C#.
At a glance, they may appear as disparate syntactical features. But look closer, and you’ll find that these constructs work together to power everything from UI frameworks to asynchronous programming, LINQ queries, and reactive systems.
This article offers a thorough exploration of delegates, events, and lambdas in C#, designed for developers seeking clarity, practical understanding, and modern usage patterns. Whether you’re a new C# programmer or a seasoned developer brushing up on foundational knowledge, this is your roadmap.
Why This Topic Matters
C# is a language that thrives on delegation and flexibility. Delegates allow methods to be passed as parameters, events provide a framework for building responsive applications, and lambda expressions bring in functional flair, allowing for concise, expressive code.
Together, these constructs:
- Power the .NET event model
- Enable asynchronous programming (e.g.,
async/await
,Task
) - Are critical to data manipulation (e.g., LINQ)
- Support decoupling and extensibility (e.g., plugin architectures, observer patterns)
Understanding how and when to use them can dramatically improve the flexibility, readability, and testability of your C# code.
Part 1: Understanding Delegates
What is a Delegate?
A delegate is a type that represents references to methods with a specific signature. In simpler terms, it’s like a pointer to a method, but type-safe and managed.
Basic Delegate Syntax
csharpCopyEditpublic delegate void Notify(); // Delegate declaration
public class Process
{
public void StartProcess(Notify notifyMethod)
{
Console.WriteLine("Process started.");
notifyMethod(); // Call delegate
}
}
Using a Delegate
csharpCopyEditclass Program
{
static void Main()
{
Process process = new Process();
process.StartProcess(DisplayMessage);
}
static void DisplayMessage()
{
Console.WriteLine("Process completed.");
}
}
When StartProcess
runs, it will call whatever method is passed via the Notify
delegate, creating loose coupling between logic and action.
Multicast Delegates
Delegates can point to multiple methods using +=
and -=
.
csharpCopyEditNotify notifyDelegate = FirstMethod;
notifyDelegate += SecondMethod;
notifyDelegate(); // Invokes both methods in order
Delegate Chaining Rules
- Delegates are immutable.
- The result of a delegate chain is the return value of the last method invoked.
- If earlier methods throw exceptions, the chain breaks.
Built-in Delegate Types
C# provides generic delegate types to reduce boilerplate:
Type | Signature | Example Use |
---|---|---|
Action | No return value | Action<string> = method that takes a string |
Func | Returns a value | Func<int, int> = method that takes int, returns int |
Predicate | Returns bool | Predicate<T> = method to test a condition |
Example:
csharpCopyEditFunc<int, int, int> add = (x, y) => x + y;
Console.WriteLine(add(3, 5)); // Outputs: 8
Part 2: Events in C#
Delegates are powerful, but on their own, they offer no restrictions. That’s where events come in.
What is an Event?
An event is a special kind of delegate that:
- Can only be invoked by the declaring class
- Can be subscribed to or unsubscribed from using
+=
and-=
Events are the backbone of the publisher-subscriber pattern, used heavily in GUI applications, logging systems, and messaging infrastructure.
Declaring and Using Events
csharpCopyEditpublic class Button
{
public event Action Clicked;
public void SimulateClick()
{
Clicked?.Invoke(); // Only the Button class can invoke it
}
}
Subscribing to Events
csharpCopyEditvar button = new Button();
button.Clicked += () => Console.WriteLine("Button was clicked!");
button.SimulateClick(); // Output: Button was clicked!
Benefits of Events
- Encapsulation: Prevents external classes from invoking the event.
- Loose coupling: The publisher doesn’t need to know who is listening.
- Scalability: Multiple subscribers can react to a single event.
Custom EventArgs
For more complex event data, use EventHandler<T>
:
csharpCopyEditpublic class MyEventArgs : EventArgs
{
public string Message { get; set; }
}
public class Publisher
{
public event EventHandler<MyEventArgs> OnPublish;
public void Publish()
{
OnPublish?.Invoke(this, new MyEventArgs { Message = "Hello" });
}
}
Unsubscribing from Events
Always unsubscribe to avoid memory leaks:
csharpCopyEditbutton.Clicked -= OnButtonClick;
This is crucial in long-lived applications (e.g., desktop or server apps).
Part 3: Lambda Expressions
Lambdas are anonymous functions. They let you write short function expressions inline.
Basic Syntax
csharpCopyEdit(parameters) => expression
Examples:
csharpCopyEditAction greet = () => Console.WriteLine("Hello");
Func<int, int> square = x => x * x;
Lambdas and Delegates
Lambdas are shorthand for defining delegates:
csharpCopyEditAction log = () => Console.WriteLine("Logging...");
Func<int, int, int> multiply = (a, b) => a * b;
Lambdas in LINQ
LINQ is where lambdas shine:
csharpCopyEditvar evenNumbers = numbers.Where(n => n % 2 == 0);
Here, n => n % 2 == 0
is a lambda that returns true
for even numbers.
Capturing Variables (Closures)
Lambdas can “close over” variables in their scope:
csharpCopyEditint factor = 2;
Func<int, int> multiplier = x => x * factor;
This is called a closure—and it’s a powerful feature in functional programming.
Lambda Block Bodies
For complex logic, use block syntax:
csharpCopyEditFunc<int, int> factorial = x =>
{
int result = 1;
for (int i = 1; i <= x; i++) result *= i;
return result;
};
Putting It All Together: A Real-World Example
Let’s build a mini event-driven app that uses all three: delegates, events, and lambdas.
Scenario: A Publisher/Subscriber System
csharpCopyEditpublic class NewsPublisher
{
public event Action<string> NewsPublished;
public void PublishNews(string news)
{
Console.WriteLine($"Publishing: {news}");
NewsPublished?.Invoke(news);
}
}
public class Subscriber
{
private string _name;
public Subscriber(string name)
{
_name = name;
}
public void Subscribe(NewsPublisher publisher)
{
publisher.NewsPublished += news => Console.WriteLine($"{_name} received: {news}");
}
}
Usage:
csharpCopyEditvar publisher = new NewsPublisher();
var alice = new Subscriber("Alice");
var bob = new Subscriber("Bob");
alice.Subscribe(publisher);
bob.Subscribe(publisher);
publisher.PublishNews("C# 13 Released!");
Output:
bashCopyEditPublishing: C# 13 Released!
Alice received: C# 13 Released!
Bob received: C# 13 Released!
This example uses:
- An event (
NewsPublished
) - A delegate (under the hood,
Action<string>
) - A lambda to handle the event inline
Best Practices
✅ Use built-in delegates like Action
, Func
, and Predicate
instead of creating your own.
✅ Unsubscribe from events when no longer needed.
✅ Prefer lambdas for short, inline logic; use methods for complex operations.
✅ Avoid null reference exceptions using null-safe invocation (?.Invoke()
).
✅ Document events so subscribers know what data to expect.
Common Pitfalls
Pitfall | Solution |
---|---|
Forgetting to unsubscribe | Use weak references or manually unsubscribe. |
Using null checks incorrectly | Always use ?.Invoke() to prevent crashes. |
Capturing loop variables | Use a local copy inside loops with lambdas. |
Using async void lambdas | Avoid; prefer async Task patterns. |
Summary: How They Work Together
Feature | Description | Typical Use |
---|---|---|
Delegate | Type-safe reference to a method | Callback functions |
Event | Publisher-subscriber wrapper over a delegate | UI, messaging |
Lambda | Concise function definition | LINQ, event handling |
These three features aren’t just syntax—they’re the glue that connects logic in a clean, composable, and expressive way in modern C# development.
Final Thoughts
If C# is a symphony of elegant syntax and scalable architecture, then delegates, events, and lambdas are its rhythmic instruments. They don’t just help your code run—they help it resonate. Together, they power everything from reactive interfaces and dynamic APIs to rich query pipelines and testable systems – Lambdas in C#.
Learning how to wield these tools is like mastering the strings of a guitar. At first, the chords seem unfamiliar. But with practice, you unlock a language of expression that is concise, powerful, and surprisingly beautiful – Lambdas in C#.
Read:
Getting Started with LINQ in C#: A Beginner’s Guide
C# for Absolute Beginners: Writing Your First Console Application
Building REST APIs with ASP.NET Core and C#: A Modern Developer’s Guide
Authentication and IAM in GCP for C# Applications: A Comprehensive Developer’s Guide
FAQs
1. What is the difference between a delegate and an event in C#?
A delegate is a type that references methods with a specific signature—it’s like a type-safe function pointer. An event is a wrapper around a delegate that restricts how it can be used. While a delegate can be invoked from anywhere, an event can only be triggered by the class that declares it, making it safer and more suitable for the publisher-subscriber pattern.
2. When should I use a lambda expression instead of a named method?
Use lambda expressions when you need short, inline logic—especially for filtering, transforming, or handling simple events. For more complex operations or reusable code, use a named method to improve readability and maintainability.
Example use of a lambda:
csharpCopyEditnumbers.Where(n => n % 2 == 0);
3. Can I pass a method as a parameter in C#?
Yes.
You can pass a method as a parameter using a delegate, or more commonly with built-in types like Action
, Func
, or Predicate
. This enables callback-style programming, allowing flexibility and decoupling between components.
Example:
csharpCopyEditvoid Execute(Action task) => task();
Execute(() => Console.WriteLine("Task executed"));
4. What are multicast delegates and how do they work?
A multicast delegate is a delegate that points to multiple methods. When invoked, it calls all the methods in its invocation list in order. They are often used in events where multiple listeners may need to respond.
csharpCopyEditAction notify = FirstMethod;
notify += SecondMethod;
notify(); // Both methods are called
Only the return value of the last method is preserved if the delegate returns a value.
5. Why should I unsubscribe from events, and how do I do it?
Failing to unsubscribe from events can lead to memory leaks, especially if the publisher outlives the subscriber. This is because event subscriptions create a reference that keeps the subscriber in memory.
To unsubscribe:
csharpCopyEditbutton.Clicked -= OnButtonClicked;
It’s especially important in long-lived applications (e.g., desktop or server apps) where memory management is critical.