Delegates, Events, and Lambdas
## Learning Objectives
- Understand delegates
- Work with events
- Master lambda expressions
- Learn anonymous methods
## Delegates
### What is a Delegate?
A delegate is a type that represents references to methods with a specific signature.
```csharp
// Delegate declaration
delegate int Calculate(int a, int b);
// Methods matching the signature
class MathOperations
{
public static int Add(int a, int b) => a + b;
public static int Multiply(int a, int b) => a * b;
}
// Usage
Calculate calc = MathOperations.Add;
int result = calc(5, 3); // 8
calc = MathOperations.Multiply;
result = calc(5, 3); // 15
```
### Generic Delegates (Built-in)
```csharp
// Func - returns a value
Func add = (a, b) => a + b;
Func double = x => x * 2;
Func getName = () => "Alice";
// Action - returns void
Action print = Console.WriteLine;
Action multiply = (a, b) => Console.WriteLine(a * b);
// Predicate - returns bool
Predicate isEven = x => x % 2 == 0;
```
## Events
### Publish-Subscribe Pattern
```csharp
class Button
{
// Public event
public event EventHandler? Clicked;
public void Click()
{
Console.WriteLine("Button clicked");
// Raise event if there are subscribers
Clicked?.Invoke(this, EventArgs.Empty);
}
}
// Subscriber
class Program
{
static void Main()
{
Button button = new Button();
// Subscribe to event
button.Clicked += OnButtonClicked;
button.Click();
}
static void OnButtonClicked(object? sender, EventArgs e)
{
Console.WriteLine("Button was clicked!");
}
}
```
### EventHandler
### Custom Event Args
```csharp
class TemperatureEventArgs : EventArgs
{
public double Temperature { get; }
public TemperatureEventArgs(double temp) => Temperature = temp;
}
class Thermometer
{
public event EventHandler? TemperatureChanged;
public void Measure(double temp)
{
if (temp != lastTemp)
{
lastTemp = temp;
TemperatureChanged?.Invoke(this, new TemperatureEventArgs(temp));
}
}
}
// Usage
Thermometer t = new Thermometer();
t.TemperatureChanged += (sender, e) => Console.WriteLine($"Temp: {e.Temperature}");
```
## Lambda Expressions
### Syntax
```csharp
// Full syntax
(int x, int y) => { return x + y; }
// Expression body (no return, no braces)
x => x * 2;
// Multiple parameters
(a, b) => a + b;
// No parameters
() => Console.WriteLine("Hello");
```
### Examples
```csharp
// With Func
Func square = x => x * x;
Func concat = (a, b) => a + b;
// With Action
Action print = msg => Console.WriteLine(msg);
// With Predicate
Predicate isPositive = x => x > 0;
// Complex logic
Func grade = score =>
{
if (score >= 90) return "A";
if (score >= 80) return "B";
return "C";
};
```
## Anonymous Methods
### Before Lambdas
```csharp
// Anonymous method
delegate int Calculate(int a, int b);
Calculate calc = delegate(int a, int b) { return a + b; };
// With events
button.Clicked += delegate(object? sender, EventArgs e)
{
Console.WriteLine("Clicked!");
};
```
## Closures
### Capturing Variables
```csharp
int multiplier = 10;
Func multiply = x => x * multiplier;
Console.WriteLine(multiply(5)); // 50
multiplier = 20;
Console.WriteLine(multiply(5)); // 200 (captures by reference!)
```
### Common Pitfall
```csharp
var actions = new List();
// Wrong - all capture same variable
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var a in actions)
a(); // Prints: 3, 3, 3
// Correct - capture value
var actions2 = new List();
for (int i = 0; i < 3; i++)
{
int captured = i; // Capture value
actions2.Add(() => Console.WriteLine(captured));
}
foreach (var a in actions2)
a(); // Prints: 0, 1, 2
```
## Delegates Combining (Multicast)
### Combining Delegates
```csharp
delegate void Notify();
Notify notify1 = () => Console.WriteLine("Handler 1");
Notify notify2 = () => Console.WriteLine("Handler 2");
Notify combined = notify1 + notify2;
combined(); // Calls both
Notify notify3 = () => Console.WriteLine("Handler 3");
combined += notify3;
combined(); // Calls all three
// Remove
combined -= notify2;
combined(); // Handler 1 and 3
```
### Return Values with Multicast
```csharp
delegate int Calculate(int x);
Calculate calc = x =>
{
Console.WriteLine($"First: {x}");
return x + 1;
};
calc += x =>
{
Console.WriteLine($"Second: {x}");
return x + 2;
};
int result = calc(5);
// Only last return value is kept!
// Output:
// First: 5
// Second: 5
// result = 7
```
## Built-in Event Patterns
### Standard Event Signature
```csharp
// Standard delegate types (already defined in .NET)
public delegate void EventHandler(object? sender, EventArgs e);
public delegate void EventHandler(object? sender, TEventArgs e);
// Common pattern
public event EventHandler? MyEvent;
public event EventHandler? SpecializedEvent;
```
## Covariance and Contravariance
### With Delegates
```csharp
// Covariance - return type can be more derived
delegate TextWriter WriterFactory();
WriterFactory factory = () => new StreamWriter("file.txt");
// Contravariance - parameter type can be less derived
Action writer = (StreamWriter sw) => sw.WriteLine("test");
Action textWriter = writer; // OK - TextWriter is base of StreamWriter
```
## Summary
- Delegate: type-safe method reference
- Func: returns value
- Action: returns void
- Predicate: returns bool
- Event: publisher-subscriber pattern
- Lambda: concise anonymous methods
- Closures capture variables by reference
- Delegates can be combined (+, -)
- Events follow standard .NET pattern
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →