12. C# Delegates, Action, Functors, Predicates, Events

Built-In Delegates in C#

C# provides several built-in delegate types that simplify common programming patterns without explicitly declaring new delegate types for each scenario.

1) Action

An Action delegate references a method that returns void. It can accept zero or more parameters, but it does not return a value.

Examples

2) Func

A Func delegate references a method that returns a value. It can accept zero or more parameters, with the last type argument representing the return type.

Examples

3) Predicate

A Predicate<T> is a delegate that returns a bool and accepts one parameter of type T. It is often used for filtering or matching criteria.

Predicate<int> isEven = (x) => x % 2 == 0;
bool check = isEven(4); // check = true

Example with Array.FindAll

int[] numbers = { 1, 2, 3, 4, 5, 6 };
int[] evenNumbers = Array.FindAll(numbers, isEven);

foreach (int num in evenNumbers)
{
    Console.WriteLine(num); // prints 2, 4, 6
}

In this example, Array.FindAll uses the predicate (isEven) to filter out elements from the array.

Events

An event in C# is built on top of delegates and provides a publish-subscribe (observer) mechanism. It’s commonly used to broadcast notifications that something has happened, allowing subscribers to handle the event without tight coupling.

Key Points

  1. Event is a Delegate

    Under the hood, an event holds a delegate instance. However, an event restricts some operations compared to a raw delegate.

  1. Event Handlers

    By convention, events often use the EventHandler or EventHandler<TEventArgs> delegate, but any delegate can be used.

  1. Visibility
    • Typically, you cannot invoke an event from outside the class that declares it.
    • You can only add (+=) or remove (=) handlers.
  1. Firing (Invoking) the Event

    Inside the class that owns the event, you typically see code that checks if the event is non-null and then invokes it.

Example

public class Publisher
{
    // Define an event of type Action or a custom delegate
    public event Action OnDataProcessed;

    public void ProcessData()
    {
        // Some processing logic
        // ...

        // Fire the event
        OnDataProcessed?.Invoke();
    }
}

public class Subscriber
{
    public void Subscribe(Publisher pub)
    {
        // Register event handler
        pub.OnDataProcessed += HandleDataProcessed;
    }

    private void HandleDataProcessed()
    {
        Console.WriteLine("Data processed event received.");
    }
}

public static class Demo
{
    public static void Main()
    {
        Publisher p = new Publisher();
        Subscriber s = new Subscriber();

        s.Subscribe(p);
        p.ProcessData();
        // The event triggers HandleDataProcessed in the Subscriber
    }
}

Differences Between Delegates and Events

  1. Access Control:
    • A raw delegate can be invoked by anyone who can access it.
    • An event can typically be invoked only within the declaring class.
  1. Subscription:
    • Both delegates and events support += and = to attach and detach handlers.
    • Events do not allow direct assignment (=) from external code.
  1. Usage Context:
    • Delegates are often used for function pointers or as parameters for callbacks.
    • Events provide a structured model for the publisher/subscriber pattern.

Reference

The content in this document is based on the original notes provided in Azerbaijani. For further details, you can refer to the original document using the following link:

Original Note - Azerbaijani Version