Menu

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

  1. Delegates are type-safe function pointers that enable callback patterns

  2. Multicast delegates can invoke multiple methods in sequence

  3. Events provide a standardized way to implement publisher-subscriber patterns

  4. Action delegates represent methods that return void

  5. Func delegates represent methods that return a value

  6. Event-driven programming enables loose coupling between components

  7. Callback patterns allow asynchronous and reactive programming

  8. 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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?