C# & ASP.NET Topic
C# Delegates, Events & Func/Action
Delegates - Type-Safe Function Pointers
Basic Delegate Concepts
csharp
// Delegate declaration - defines a method signature
public delegate void SimpleDelegate(string message);
public delegate int MathOperation(int a, int b);
public delegate bool FilterCondition(int number);
class DelegateExamples
{
// Methods that match delegate signatures
public static void DisplayMessage(string message)
{
Console.WriteLine($"Message: {message}");
}
public static void ShowWarning(string warning)
{
Console.WriteLine($"WARNING: {warning}");
}
public static int Add(int a, int b) => a + b;
public static int Multiply(int a, int b) => a * b;
public static bool IsEven(int number) => number % 2 == 0;
public static bool IsPositive(int number) => number > 0;
}
Delegate Instantiation and Invocation
csharp
public class BasicDelegateUsage
{
public static void Demonstrate()
{
// Creating delegate instances
SimpleDelegate messageDelegate = new SimpleDelegate(DelegateExamples.DisplayMessage);
SimpleDelegate warningDelegate = DelegateExamples.ShowWarning; // Shorthand syntax
// Invoking delegates
messageDelegate("Hello World!");
warningDelegate("System overload!");
// Math operations with delegates
MathOperation mathDelegate = DelegateExamples.Add;
int result = mathDelegate(10, 5); // 15
mathDelegate = DelegateExamples.Multiply;
result = mathDelegate(10, 5); // 50
// Delegates with return values and parameters
FilterCondition filter = DelegateExamples.IsEven;
bool test = filter(4); // true
test = filter(3); // false
}
}
Multicast Delegates
Chaining Multiple Methods
csharp
public class MulticastDelegateDemo
{
public static void Method1() => Console.WriteLine("Method 1 executed");
public static void Method2() => Console.WriteLine("Method 2 executed");
public static void Method3() => Console.WriteLine("Method 3 executed");
public static void DemonstrateMulticast()
{
// Delegate that returns void
Action multicastDelegate = Method1;
// Adding methods to the invocation list
multicastDelegate += Method2;
multicastDelegate += Method3;
Console.WriteLine("Invoking multicast delegate:");
multicastDelegate(); // Calls Method1, Method2, Method3 in order
// Removing methods
multicastDelegate -= Method2;
Console.WriteLine("\nAfter removing Method2:");
multicastDelegate(); // Calls Method1, Method3
// Getting invocation list
var invocationList = multicastDelegate.GetInvocationList();
Console.WriteLine($"\nMethods in delegate: {invocationList.Length}");
}
}
Multicast Delegates with Return Values
csharp
public delegate int NumberProcessor(int number);
public class MulticastReturnValues
{
public static int Double(int n) => n * 2;
public static int Square(int n) => n * n;
public static int Increment(int n) => n + 1;
public static void Demonstrate()
{
NumberProcessor processor = Double;
processor += Square;
processor += Increment;
// When invoking multicast delegate with return value,
// only the last method's return value is returned
int result = processor(5);
Console.WriteLine($"Result: {result}"); // 6 (from Increment)
// To get all return values, invoke each separately
foreach (NumberProcessor del in processor.GetInvocationList())
{
int individualResult = del(5);
Console.WriteLine($"Individual result: {individualResult}");
}
// Output:
// Individual result: 10 (Double)
// Individual result: 25 (Square)
// Individual result: 6 (Increment)
}
}
Events - Publisher-Subscriber Pattern
Basic Event Implementation
csharp
// Event publisher
public class Button
{
// Event declaration
public event EventHandler Click;
public event EventHandler<string> TextChanged;
private string _text;
public string Text
{
get => _text;
set
{
if (_text != value)
{
_text = value;
OnTextChanged(value);
}
}
}
// Method to raise the event
protected virtual void OnClick()
{
Click?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnTextChanged(string newText)
{
TextChanged?.Invoke(this, newText);
}
public void Press()
{
Console.WriteLine("Button pressed!");
OnClick();
}
}
// Event subscriber
public class Logger
{
public void LogButtonClick(object sender, EventArgs e)
{
Console.WriteLine($"Button was clicked at {DateTime.Now}");
}
public void LogTextChange(object sender, string newText)
{
Console.WriteLine($"Text changed to: {newText}");
}
}
// Usage
public class EventDemo
{
public static void Demonstrate()
{
Button button = new Button();
Logger logger = new Logger();
// Subscribe to events
button.Click += logger.LogButtonClick;
button.TextChanged += logger.LogTextChange;
// Trigger events
button.Press();
button.Text = "Hello World";
// Unsubscribe
button.Click -= logger.LogButtonClick;
}
}
Custom Event Arguments
csharp
// Custom event arguments
public class TemperatureChangedEventArgs : EventArgs
{
public double OldTemperature { get; }
public double NewTemperature { get; }
public DateTime ChangeTime { get; }
public TemperatureChangedEventArgs(double oldTemp, double newTemp)
{
OldTemperature = oldTemp;
NewTemperature = newTemp;
ChangeTime = DateTime.Now;
}
}
// Publisher with custom event args
public class Thermostat
{
private double _temperature;
// Event with custom EventArgs
public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;
public double Temperature
{
get => _temperature;
set
{
if (_temperature != value)
{
double oldTemp = _temperature;
_temperature = value;
OnTemperatureChanged(oldTemp, value);
}
}
}
protected virtual void OnTemperatureChanged(double oldTemp, double newTemp)
{
TemperatureChanged?.Invoke(this,
new TemperatureChangedEventArgs(oldTemp, newTemp));
}
}
// Subscriber
public class TemperatureMonitor
{
public void OnTemperatureChanged(object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"Temperature changed from {e.OldTemperature}°C " +
$"to {e.NewTemperature}°C at {e.ChangeTime}");
if (Math.Abs(e.NewTemperature - e.OldTemperature) > 5)
{
Console.WriteLine("*** Significant temperature change detected! ***");
}
}
}
// Usage
var thermostat = new Thermostat();
var monitor = new TemperatureMonitor();
thermostat.TemperatureChanged += monitor.OnTemperatureChanged;
thermostat.Temperature = 20; // No output - initial value
thermostat.Temperature = 25; // Normal change
thermostat.Temperature = 35; // Significant change
Func and Action Delegates
Action Delegates (No Return Value)
csharp
public class ActionDelegateExamples
{
public static void Demonstrate()
{
// Action - no parameters, no return value
Action simpleAction = () => Console.WriteLine("Simple action");
simpleAction();
// Action<T> - one parameter
Action<string> messageAction = (msg) => Console.WriteLine($"Message: {msg}");
messageAction("Hello Action!");
// Action<T1, T2> - two parameters
Action<int, int> mathAction = (x, y) => Console.WriteLine($"{x} + {y} = {x + y}");
mathAction(10, 20);
// Action with multiple parameters
Action<string, int, bool> complexAction = (name, age, isActive) =>
{
string status = isActive ? "active" : "inactive";
Console.WriteLine($"{name} is {age} years old and is {status}");
};
complexAction("John", 25, true);
}
}
Func Delegates (With Return Value)
csharp
public class FuncDelegateExamples
{
public static void Demonstrate()
{
// Func<TResult> - no parameters, returns TResult
Func<int> randomFunc = () => new Random().Next(1, 100);
int randomNumber = randomFunc();
Console.WriteLine($"Random number: {randomNumber}");
// Func<T, TResult> - one parameter, returns TResult
Func<string, int> lengthFunc = (text) => text.Length;
int length = lengthFunc("Hello World");
Console.WriteLine($"Length: {length}");
// Func<T1, T2, TResult> - two parameters, returns TResult
Func<int, int, int> addFunc = (a, b) => a + b;
Func<int, int, int> multiplyFunc = (a, b) => a * b;
Console.WriteLine($"Add: {addFunc(5, 3)}");
Console.WriteLine($"Multiply: {multiplyFunc(5, 3)}");
// Complex Func with multiple parameters
Func<string, int, bool, string> describeFunc = (name, age, isStudent) =>
{
string studentStatus = isStudent ? "a student" : "not a student";
return $"{name} is {age} years old and is {studentStatus}";
};
string description = describeFunc("Alice", 20, true);
Console.WriteLine(description);
}
}
Practical Event-Driven Programming Examples
Order Processing System
csharp
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public decimal TotalAmount { get; set; }
public DateTime OrderDate { get; set; }
}
public class OrderEventArgs : EventArgs
{
public Order Order { get; }
public DateTime ProcessedAt { get; }
public OrderEventArgs(Order order)
{
Order = order;
ProcessedAt = DateTime.Now;
}
}
// Order Processor (Publisher)
public class OrderProcessor
{
public event EventHandler<OrderEventArgs> OrderReceived;
public event EventHandler<OrderEventArgs> OrderProcessed;
public event EventHandler<OrderEventArgs> OrderCompleted;
public event EventHandler<string> ErrorOccurred;
public void ProcessOrder(Order order)
{
try
{
OnOrderReceived(order);
// Simulate processing
Console.WriteLine($"Processing order {order.OrderId}...");
Thread.Sleep(1000); // Simulate work
OnOrderProcessed(order);
// Simulate completion
Thread.Sleep(500);
OnOrderCompleted(order);
}
catch (Exception ex)
{
OnErrorOccurred($"Error processing order {order.OrderId}: {ex.Message}");
}
}
protected virtual void OnOrderReceived(Order order)
{
OrderReceived?.Invoke(this, new OrderEventArgs(order));
}
protected virtual void OnOrderProcessed(Order order)
{
OrderProcessed?.Invoke(this, new OrderEventArgs(order));
}
protected virtual void OnOrderCompleted(Order order)
{
OrderCompleted?.Invoke(this, new OrderEventArgs(order));
}
protected virtual void OnErrorOccurred(string errorMessage)
{
ErrorOccurred?.Invoke(this, errorMessage);
}
}
// Subscribers
public class OrderLogger
{
public void OnOrderReceived(object sender, OrderEventArgs e)
{
Console.WriteLine($"[LOG] Order received: {e.Order.OrderId} from {e.Order.CustomerName}");
}
public void OnOrderProcessed(object sender, OrderEventArgs e)
{
Console.WriteLine($"[LOG] Order processed: {e.Order.OrderId} at {e.ProcessedAt}");
}
public void OnOrderCompleted(object sender, OrderEventArgs e)
{
Console.WriteLine($"[LOG] Order completed: {e.Order.OrderId}");
}
public void OnError(object sender, string error)
{
Console.WriteLine($"[ERROR] {error}");
}
}
public class EmailNotifier
{
public void OnOrderCompleted(object sender, OrderEventArgs e)
{
Console.WriteLine($"[EMAIL] Sent confirmation to {e.Order.CustomerName} for order {e.Order.OrderId}");
}
}
public class InventoryManager
{
public void OnOrderProcessed(object sender, OrderEventArgs e)
{
Console.WriteLine($"[INVENTORY] Updated stock for order {e.Order.OrderId}");
}
}
// Usage
var processor = new OrderProcessor();
var logger = new OrderLogger();
var emailer = new EmailNotifier();
var inventory = new InventoryManager();
// Subscribe to events
processor.OrderReceived += logger.OnOrderReceived;
processor.OrderProcessed += logger.OnOrderProcessed;
processor.OrderProcessed += inventory.OnOrderProcessed;
processor.OrderCompleted += logger.OnOrderCompleted;
processor.OrderCompleted += emailer.OnOrderCompleted;
processor.ErrorOccurred += logger.OnError;
// Process orders
var order = new Order
{
OrderId = 1,
CustomerName = "John Doe",
TotalAmount = 99.99m
};
processor.ProcessOrder(order);
File System Watcher with Events
csharp
public class FileWatcher
{
public event EventHandler<string> FileCreated;
public event EventHandler<string> FileDeleted;
public event EventHandler<string> FileChanged;
public event EventHandler<string> FileRenamed;
private FileSystemWatcher _watcher;
private string _watchPath;
public FileWatcher(string path)
{
_watchPath = path;
_watcher = new FileSystemWatcher(path);
// Set up event handlers for the FileSystemWatcher
_watcher.Created += (s, e) => OnFileCreated(e.FullPath);
_watcher.Deleted += (s, e) => OnFileDeleted(e.FullPath);
_watcher.Changed += (s, e) => OnFileChanged(e.FullPath);
_watcher.Renamed += (s, e) => OnFileRenamed(e.FullPath);
_watcher.EnableRaisingEvents = true;
}
protected virtual void OnFileCreated(string filePath)
{
FileCreated?.Invoke(this, filePath);
}
protected virtual void OnFileDeleted(string filePath)
{
FileDeleted?.Invoke(this, filePath);
}
protected virtual void OnFileChanged(string filePath)
{
FileChanged?.Invoke(this, filePath);
}
protected virtual void OnFileRenamed(string filePath)
{
FileRenamed?.Invoke(this, filePath);
}
}
// Event handlers using Action/Func
public class FileMonitor
{
private Action<string> _onFileCreated;
private Action<string> _onFileChanged;
private Func<string, bool> _shouldProcessFile;
public FileMonitor()
{
// Configure actions
_onFileCreated = (file) =>
{
Console.WriteLine($"New file detected: {file}");
if (_shouldProcessFile?.Invoke(file) == true)
{
ProcessFile(file);
}
};
_onFileChanged = (file) =>
{
Console.WriteLine($"File changed: {file}");
};
_shouldProcessFile = (file) =>
{
string extension = Path.GetExtension(file).ToLower();
return extension == ".txt" || extension == ".csv";
};
}
public void SubscribeToWatcher(FileWatcher watcher)
{
watcher.FileCreated += (s, file) => _onFileCreated(file);
watcher.FileChanged += (s, file) => _onFileChanged(file);
}
private void ProcessFile(string filePath)
{
Console.WriteLine($"Processing file: {filePath}");
// File processing logic here
}
}
Callback Patterns
Traditional Callback with Delegates
csharp
public class DataProcessor
{
// Callback delegate
public delegate void ProcessingCompleteCallback(string result, bool success);
public delegate void ProcessingErrorCallback(string errorMessage);
public void ProcessData(string data,
ProcessingCompleteCallback completeCallback,
ProcessingErrorCallback errorCallback)
{
try
{
Console.WriteLine($"Processing data: {data}");
// Simulate processing time
Thread.Sleep(2000);
// Simulate result
string result = $"Processed: {data.ToUpper()}";
// Invoke callback
completeCallback?.Invoke(result, true);
}
catch (Exception ex)
{
errorCallback?.Invoke($"Processing failed: {ex.Message}");
}
}
}
// Usage with traditional callbacks
var processor = new DataProcessor();
processor.ProcessData(
"hello world",
// Success callback
(result, success) =>
{
Console.WriteLine($"Processing completed: {result}");
},
// Error callback
(error) =>
{
Console.WriteLine($"Error: {error}");
}
);
Modern Callback with Action/Func
csharp
public class ModernDataProcessor
{
// Using Action/Func for callbacks
public async Task ProcessDataAsync(string data,
Action<string, bool> completeCallback,
Action<string> errorCallback = null)
{
try
{
await Task.Delay(2000); // Simulate async work
if (data.Contains("error"))
throw new InvalidOperationException("Simulated error");
string result = $"Processed: {data.ToUpper()}";
completeCallback?.Invoke(result, true);
}
catch (Exception ex)
{
errorCallback?.Invoke($"Async processing failed: {ex.Message}");
}
}
// Using Func for transformation callbacks
public List<string> TransformData(List<string> data, Func<string, string> transformer)
{
return data.Select(transformer).ToList();
}
// Using Predicate for filtering
public List<string> FilterData(List<string> data, Predicate<string> filter)
{
return data.Where(item => filter(item)).ToList();
}
}
// Usage
var modernProcessor = new ModernDataProcessor();
// Process with callbacks
await modernProcessor.ProcessDataAsync(
"test data",
(result, success) => Console.WriteLine($"Result: {result}"),
error => Console.WriteLine($"Error: {error}")
);
// Transform data with Func
var names = new List<string> { "john", "jane", "bob" };
var upperNames = modernProcessor.TransformData(names, name => name.ToUpper());
// Filter data with Predicate
var longNames = modernProcessor.FilterData(names, name => name.Length > 3);
Advanced Delegate Patterns
Delegate Composition
csharp
public class DelegateComposition
{
public static void Demonstrate()
{
// Basic functions
Func<int, int> doubleIt = x => x * 2;
Func<int, int> squareIt = x => x * x;
Func<int, int> increment = x => x + 1;
// Compose functions manually
Func<int, int> doubleThenSquare = x => squareIt(doubleIt(x));
Console.WriteLine($"Double then square 3: {doubleThenSquare(3)}"); // 36
// Generic composition function
Func<T, T> Compose<T>(params Func<T, T>[] functions)
{
return input => functions.Aggregate(input, (current, func) => func(current));
}
var complexOperation = Compose(doubleIt, squareIt, increment);
Console.WriteLine($"Complex operation on 2: {complexOperation(2)}"); // 17
}
}
Strategy Pattern with Delegates
csharp
public class PaymentProcessor
{
private Func<decimal, bool> _paymentStrategy;
public void SetPaymentStrategy(Func<decimal, bool> strategy)
{
_paymentStrategy = strategy;
}
public bool ProcessPayment(decimal amount)
{
if (_paymentStrategy == null)
throw new InvalidOperationException("Payment strategy not set");
return _paymentStrategy(amount);
}
}
// Usage with different strategies
var processor = new PaymentProcessor();
// Credit card strategy
processor.SetPaymentStrategy(amount =>
{
Console.WriteLine($"Processing ${amount} via Credit Card");
return true; // Simulate success
});
bool result = processor.Purchase(100.00m);
// PayPal strategy
processor.SetPaymentStrategy(amount =>
{
Console.WriteLine($"Processing ${amount} via PayPal");
return amount <= 500.00m; // PayPal has limit
});
result = processor.Purchase(600.00m); // Would return false
Best Practices
1. Event Design Guidelines
csharp
public class ProperEventDesign
{
// Use EventHandler<T> for custom events
public event EventHandler<CustomEventArgs> CustomEvent;
// Use the standard pattern for raising events
protected virtual void OnCustomEvent(CustomEventArgs e)
{
CustomEvent?.Invoke(this, e);
}
// Always check for null before invoking
private void SafeEventInvocation()
{
var handlers = CustomEvent;
if (handlers != null)
{
foreach (EventHandler<CustomEventArgs> handler in handlers.GetInvocationList())
{
try
{
handler(this, new CustomEventArgs());
}
catch (Exception ex)
{
// Handle individual handler exceptions
Console.WriteLine($"Handler failed: {ex.Message}");
}
}
}
}
}
2. Delegate Usage Patterns
csharp
public class DelegateBestPractices
{
// Use Action/Func instead of custom delegates when possible
public void ProcessData(Action<string> callback) { }
public void TransformData(Func<string, string> transformer) { }
// Use method groups instead of lambda when possible
public void DemonstrateMethodGroups()
{
Action<string> action1 = Console.WriteLine; // Method group - better
Action<string> action2 = msg => Console.WriteLine(msg); // Lambda - creates closure
Func<int, bool> func1 = IsPositive; // Method group
Func<int, bool> func2 = x => x > 0; // Lambda
}
private static bool IsPositive(int number) => number > 0;
// Be careful with captured variables in lambdas
public void ClosureExample()
{
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int copy = i; // Capture current value
actions.Add(() => Console.WriteLine(copy));
}
foreach (var action in actions)
{
action(); // Outputs 0, 1, 2
}
}
}
Key Points Summary
-
Delegates are type-safe function pointers that enable callback patterns
-
Multicast delegates can invoke multiple methods in sequence
-
Events provide a standardized way to implement publisher-subscriber patterns
-
Action delegates represent methods that return void
-
Func delegates represent methods that return a value
-
Event-driven programming enables loose coupling between components
-
Callback patterns allow asynchronous and reactive programming
-
Delegate composition enables building complex operations from simple functions
This comprehensive guide shows how delegates, events, and Func/Action enable powerful patterns for event-driven programming, callbacks, and flexible method invocation in C#!
Comments (0)
No comments yet. Be the first to comment!