Mastering Action and Func Delegates in C#: A Comprehensive Guide
Tags: csharp, dotnetcore, delegate, programming
Published: 2023-01-07 16:13:26

In this article, we are going to learn about implementing **Action** and **Func** delegates in **C#**. 

**Delegates** are an essential part of the internal implementations of a broader range of functionalities in the **.Net** ecosystem.

**Action** and **Func** are built-in delegates that the framework provides, easing the burden of cluttering the code base with similar type definitions. 

They are an essential component of **functional programming** in **C#**.

We have heard the famous **OOP** proverb **"Programming to an interface, not to an implementation"** or **"Programming to an abstraction, not to an implementation."** That's exactly what delegates can help us with.

After understanding delegates in general, we will continue shaping our knowledge around Action and Func, solidifying our understanding with concrete examples of how to use them in .Net. 

Let's get going.

## What is a Delegate?

A **delegate is a type-safe reference to methods** defined in a class. But what is type-safety? And why is it important when it comes to delegates?

The delegate defines the exact type of parameters it accepts and the type it returns. Compatibility between the method and the delegate is possible if the method has the same signature as the delegate. 

Let's define a delegate.

```
public delegate string[] Filter(string str);
```

The delegate `Filter` takes a `string` parameter and returns a `string` array.

What about methods compatibility with `Filter` delegate? 

Let's take a look:

```
public static string[] SplitText(string str)
{
    return str.Split(' ');
}

public static int TextLength(string str)
{
    return str.Split(' ').Length;
}

```
The `SplitText` method has the same signature as the `Filter` delegate, but the `TextLength` method does not match the `Filter` argument type due to a dissimilar return type.

What happens when we assign `TextLength` to the `Filter` delegate? No surprises here:

```
Filter filter = TextLength; // Error: Cannot convert from 'method group' to 'Filter'
```
The compiler will issue an error when trying to assign `TextLength` as a method reference to the `Filter` delegate. 

The type safety feature of delegates acts as a binding contract that methods must adhere to.

## Why Action and Func?

Before the introduction of **Generics** in the .Net framework 2.0, a new delegate definition was required for varying arguments or return types. 

Let's define a few delegates:

```
public delegate void Log(string message);

public delegate void TotalDiscount(decimal discount);

```
The `Log` and `TotalDiscount` delegates differ only in terms of the type of argument they expect. Isn't it better to have just one delegate definition for both of them?

```
public delegate void Action<T>(T arg);
```

The `Action` delegate is compatible with any method that has no return type and accepts one argument of type `T`.

What about a delegate with a return type other than `void`?

```
public delegate string Filter(string phrase);

public delegate string[] Split(string phrase);
```

Not to worry, we can define a delegate using generics to match both the `Filter` and `Split` delegates:

`public delegate TResult Func<T, TResult>(T arg);`

The `Func` delegate accepts an argument of type `T` and returns type `Tresult`. This matches any method definition that has one argument of type `T` and returns a type other than `void`.

We don't need to define similar delegates anymore. We can use generic `Action` and `Func` instead, correct?

Consider this: a delegate could take a varying number of parameters with or without a return type other than void.

Let's define a few generic delegates:

```
public delegate void Action<Tin1, Tin2>(Tin1 arg1, Tin2 arg2);

public delegate Tout Func<Tin1, Tin2, Tout>(Tin1 arg1, Tin2 arg2);
```

`Action` and `Func` take two arguments. Similarly, we could define them with as many arguments as we wish. 

Thankfully, `Action` and `Func` delegates were made available in the .Net Framework version 3.5 under the `System` namespace. In most cases, we don't need to define them ourselves.

The **Action** delegate has a return type of `void` whereas the **Func** delegate has a return type other than `void`. Both can take up to **16** parameters.

### How Do We Declare/Initialize Action and Func?

The Action delegate have generic as well as non-generic definitions. We can declare a variable of a type Action that takes no argument or a more generic one as `Action<T1>`, `Action<T2>`, `Action<T3>` all the way to `Action<T1, T2, T3, ............, T16>` that accepts from 1 to 16 arguments.

Let’s look at a few inbuilt `Action` declarations and initialization:

```
Action notify = () =>
{
    Console.WriteLine("Actions are great.");
};

Action<int> doMath = (x) =>
{
    Console.WriteLine($"The square of {x} is {x * x}");
};

Action<int, decimal> calculateDiscount = (x, y) =>
{
    Console.WriteLine($"The discount is {x * y}");
};
```

The `notify`, `doMath` and `calculateDiscount` variables of type `Action` have varying lengths of arguments initialized with compatible inline method definitions.

The Func delegates are all generic as they always have a return type associated with them which is not the case with Action delegate that always returns void. 

The `Func<TResult>` delegate takes no argument and returns TResult. We can declare `Func<T1, TResult>`, `Func<T1, T2, TResult>` all the way to `Func<T1, T2, T3, ........., T16, TResult>` that accepts from 1 to 16 arguments always returning the value of type `TResult`.

Let’s see a few inbuilt `Func` declarations and initialization:

```
Func<bool> isTrue = () => true;

Func<string, string[]> ToArray = (s) => s.Split(' ');

Func<string, int, string> Concat = (s, i) => s + i.ToString();

```

The last parameter in the `Func` declaration denotes the return type of the delegate. The `isTrue` has no argument, it returns a `boolean` value. The `ToArray` accepts a string and returns an `array` of strings. The `Concat` accepts two arguments of type `int` and `string`, and it returns a `string` value. 

Enough talking, let’s have a practical demonstration to understand Action and Func delegates better.

### Action and Func as Callback Functions

Action and Func are special types of variables that hold methods as references. 

The variables of type Action and Func can be used just like any other variables, but they can also be invoked like a method. 

This can be useful when you need to pass a method as an argument to another method, or when you want to store a method in a variable for later use.

Let’s unravel it:

```
public static IEnumerable<int> ProcessList(IEnumerable<int> nums, Func<int, int> func)
{
    foreach (var num in nums)
    {
        yield return func(num);
    }
}
```

The `ProcessList` method accepts two arguments, one of which is a `func` delegate of type `Func<int, int>`. The `ProcessList` method has no idea of what method the `func` delegate refers to, as long as the method passed to it adheres to the `func` definition. 

This is an example of how you can use the Func delegate to pass a method as an argument to another method. The `ProcessList` method can then use the `func` delegate to invoke the method passed to it. 

### Simple Callback Pattern Implementation With Action and Func

Having a working solution is the best thing. We will create a small application to experiment with the concept of callbacks using Action and Func.

We start with creating our model classes first:

```
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Course> Courses { get; set; } = new List<Course>();

    public static IEnumerable<Student> DummyStudents()
    {
        return new List<Student>
        {
            new Student { Id = 1, Name = "Rahul" },
            new Student { Id = 2, Name = "Vikas" },
            new Student { Id = 3, Name = "Neha" },
            new Student { Id = 4, Name = "Abhi" },
            new Student { Id = 5, Name = "Lara" }
        };
    }
}

```

The `Student` class has an `Id` and `Name` property. Each Student has a `Courses` collection. It also defines a static helper method called `DummyStudents` which populates some dummy data for the `Student` objects.

Next, we create the `Course` class with an `Id`, `Name`, and `Price` property. The Course class also contains the `DummyCourses` static method that returns a list of `courses`:

```
public class Course
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public static IEnumerable<Course> DummyCourses()
    {
        return new List<Course>
        {
            new Course { Id = 1, Name = "C#", Price = 99.99m },
            new Course { Id = 2, Name = "Java", Price = 149.99m },
            new Course { Id = 3, Name = "Python", Price = 199.99m },
            new Course { Id = 4, Name = "JavaScript", Price = 149.99m },
            new Course { Id = 5, Name = "Ruby", Price = 199.99m },
            new Course { Id = 6, Name = "PHP", Price = 99.99m }
        };
    }
}
```

Now let's create a `StudentService` class. We want to declare a method to filter students that match a specific condition:

```
public static class StudentService
{
    public static Student GetStudent(IEnumerable<Student> students, Func<Student, bool> predicate)
    {
        foreach (var student in students)
        {
            if (predicate(student))
            {
                return student;
            }
        }

        return default;
    }
}
```

The `GetStudent()` method takes a list of `students` and applies a special Func called `predicate`, iterating over the list. 

Next, we are going to use the `GetStudent()` method of the `StudentService` class to filter students and see the result in our `Main` method inside our `Program` class.

```
internal class Program
{
    static void Main(string[] args)
    {
        var students = Student.DummyStudents();
        var student = StudentService.GetStudent(students, (s) => s.Id == 3);

        Print(student, (s) => Console.WriteLine($"Id: {s.Id}, Name: {s.Name}"));
    }

    public static void Print<T>(T arg, Action<T> printAction)
    {
        Console.WriteLine("Let's look at the output..");
        printAction(arg);
    }
}
```

Inside our `Main` method, we first populate the dummy students. Then, we call the `GetStudent()` method of the `StudentService` class, passing in the students and an inline method that matches the `Func<Student, bool>` delegate.

Looking at `(s) => s.Id == 1`, we see that the lambda expression returns true if the id matches 1. 

We call the `Print` method to display the result, verifying that indeed we receive a student with an id of 1:

```
Let's look at the output..
Id: 1 Name: Rahul
```

We should not ignore the awesomeness of the `Print` method. It can operate on any argument of type `T` and call any action on it.

We can modify the display behavior by updating the inline function `(s) => Console.WriteLine($"Id: {s.Id} Name: {s.Name}")` that we pass to the `Print` method, matching the generic variable of type `Action<T>`.

The dynamic nature of the `Print` method allows it to operate on any argument of type `T` and call any action on it, which means that it can save the result in memory instead of printing it on the console. 

This demonstrates the concept of **"programming to an abstraction, not to an implementation"** which means that you should design your code to depend on abstractions (such as interfaces or delegates) rather than on specific implementations. 

This allows you to change the implementation of a component without affecting the rest of the system. 

One of the features of a delegate is the ability to hold references to more than one method. This is called multicasting, and it allows you to invoke multiple methods with a single delegate invocation. 

This can be useful when you want to notify multiple listeners about an event, or when you want to perform multiple actions in response to a single event.

We are going to extend our solution to demonstrate how Action or Func can call more than one method.

### Simple Pub/Sub Pattern Using Action

Let’s extend the `StudentService` class to include the method `EnrollStudentToCourse()`. 

We want to send notifications to different departments whenever a student gets enrolled in a new course:

```
public static class StudentService
{
    public static Student GetStudent(IEnumerable<Student> students, Func<Student, bool> predicate)
    {
        foreach (var student in students)
        {
            if (predicate(student))
            {
                return student;
            }
        }

        return default;
    }

    public static void EnrollStudentToCourse(Student student, Course course, Action<Student> notify)
    {
        student.Courses.Add(course);
        notify(student);
    }
}
```

The `EnrollStudentToCourse()` method adds a new course to the student's courses list, and then calls the notify action. 

Next, we update the `Program` class to include the `NotifyAdmin()` and `NotifyAccount()` methods:

```
public static void NotifyAdmin(Student student)
{
    Console.WriteLine($"{student.Name} is now enrolled in {student.Courses.Last().Name}.");
}

public static void NotifyAccount(Student student)
{
    var course = student.Courses.Last();
    var amount = course.Price;
    Console.WriteLine($"Please deduct {amount}$ from {student.Name}'s account.");
}
```

Both `NotifyAdmin()` and `NotifyAccount()` matches the `Action<Student>` that the `EnrollStudentToCourse()` of the class `StudentService` accepts as one of the arguments. 

All is set, we can extend the `Main` to enroll the student in a new course and see if the process involves the notification to the Admin and Account departments:

```
static void Main(string[] args)
{
    // fetch students and courses
    var students = Student.DummyStudents();
    var courses = Course.DummyeCourses();
    // get student with a name: Rahul
    var student_Rahul = StudentService.GetStudent(students, (s) =>
    // create an Action<Student> variable and assign a method
    Action<Student> sendNotifications = NotifyAdmin;
    // add another method to the sendNotifications
    sendNotifications += NotifyAccount;
    // call EnrollStudentToCourse passing student, course and send
    StudentService.EnrollStudentToCourse(student_Rahul, courses.Fi
    console.ReadLine();
}
```

We define a `sendNotifications` variable of type `Action<Student>` in `Main`, assigning `NotifyAdmin` to it. 

We also add `NotifyAccount` using `sendNotification += NotifyAccount`. 

The call to `EnrollStudentToCourse()` invokes all the subscribers of the `sendNotifications` action.

Indeed, from the result, we see that both `NotifyAdmin()` and `NotifyAccount()` gets called when a student gets enrolled in a course.

```
Rahul is now enrolled in ASP.NET CORE API course
Please deduct amount: 100 dollars from Rahul
```

Undeniably, we have just scratched the surface a bit, there is more to **Action** and **Func** to know about. For now, let's conclude our discussion.

## Conclusion

Action and Func are the inbuilt delegates. We tried to answer the question - why they are important and how we can provide a layer of abstraction using delegates. 

In this article, we implemented the callback functionality using Action & Func. We also achieved a simple pub/sub model using Action. Hopefully, this article clears the air on the usability of Action and Func.