Action and Func Delegates

Vaibhav • September 11, 2025

In the previous articles, we explored delegates, anonymous methods, and lambda expressions - all of which allow us to treat methods as values and pass behavior around in our programs. Now it’s time to introduce two built-in delegate types that make this even easier: Action and Func. These generic delegates are part of the .NET framework and are used extensively in modern C# code, especially when working with collections, events, and functional programming patterns.

In this article, we’ll explore what Action and Func delegates are, how they differ from custom delegates, how to use them with lambda expressions, and how they simplify common programming tasks. We’ll also look at practical examples and best practices for using them effectively.

What Are Action and Func?

Action and Func are generic delegate types defined in the System namespace. They allow you to represent methods with specific signatures without having to declare your own delegate types.

The key difference between them is:

  • Action represents a method that returns void.
  • Func represents a method that returns a value.

Both can take zero or more parameters. This makes them incredibly flexible and useful for passing behavior around in your code.

Using Action

Let’s start with Action. It’s used when you want to execute a method that doesn’t return anything. You can use it with lambda expressions, anonymous methods, or named methods.

Action greet = () => Console.WriteLine("Hello!");
greet(); // Output: Hello!

Here, we define an Action that takes no parameters and simply prints a message. The lambda expression () => Console.WriteLine("Hello!") matches the signature of Action, so it can be assigned directly.

You can also define Actions with parameters:

Action<string> log = message => Console.WriteLine("Log: " + message);
log("System started"); // Output: Log: System started

This Action takes a single string parameter and logs it. You can define Actions with up to 16 parameters, though in practice you’ll rarely need more than 2–3.

Using Func

Func is used when you want to execute a method that returns a value. The last type parameter in Func always represents the return type.

Func<int> getRandom = () => new Random().Next(1, 100);
Console.WriteLine(getRandom()); // Output: (some number between 1 and 99)

This Func takes no parameters and returns an int. You can also define Funcs with parameters:

Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // Output: 7

This Func takes two int parameters and returns their sum. The signature matches Func<int, int, int> - two inputs, one output.

Why Use Action and Func?

Before Action and Func, you had to declare your own delegate types for every method signature you wanted to use. This added boilerplate and made code harder to read. With Action and Func, you can use built-in types that are already optimized and well-understood.

They also work seamlessly with lambda expressions, making your code more concise and expressive. For example, instead of writing:

public delegate int Calculator(int x, int y);
Calculator calc = (a, b) => a + b;

You can simply write:

Func<int, int, int> calc = (a, b) => a + b;

This reduces clutter and improves readability - especially in larger codebases.

Using Action and Func with Collections

One of the most common uses of Action and Func is with collections. Methods like List.ForEach, List.Find, and List.ConvertAll accept these delegates to customize behavior.

List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
names.ForEach(name => Console.WriteLine("Hello, " + name));

Here, we use an Action<string> to greet each name in the list. The lambda name => Console.WriteLine(...) is passed directly to ForEach.

You can also use Func to transform collections:

List<int> numbers = new List<int> { 1, 2, 3 };
List<string> labels = numbers.ConvertAll(n => "Number: " + n);

labels.ForEach(label => Console.WriteLine(label));
// Output:
// Number: 1
// Number: 2
// Number: 3

The lambda n => "Number: " + n is a Func<int, string> - it takes an int and returns a string. This makes it perfect for transforming data.

Using Action and Func in Events

You can also use Action and Func in event systems - especially when you want to define handlers inline. For example:

public event Action<string> OnMessage;

OnMessage += msg => Console.WriteLine("Received: " + msg);
OnMessage?.Invoke("Hello"); // Output: Received: Hello

This pattern is common in UI frameworks, reactive systems, and custom event buses. It avoids the need for separate handler methods and keeps logic close to the event source.

Returning Action and Func from Methods

You can return Action and Func delegates from methods - allowing you to generate behavior dynamically. This is useful for building factories, pipelines, and higher-order functions.

Func<int, int> MakeMultiplier(int factor)
{
    return x => x * factor;
}

var triple = MakeMultiplier(3);
Console.WriteLine(triple(5)); // Output: 15

The method MakeMultiplier returns a Func<int, int> that multiplies its input by a given factor. This pattern is common in functional programming and dynamic APIs.

Composing Actions and Funcs

You can compose multiple Actions or Funcs to build complex behavior from simple parts. For example:

Action<string> logToConsole = msg => Console.WriteLine("Console: " + msg);
Action<string> logToFile = msg => Console.WriteLine("File: " + msg); // Simulated

Action<string> combined = logToConsole + logToFile;
combined("System update");
// Output:
// Console: System update
// File: System update

This works because Action is a multicast delegate. You can chain multiple Actions using + or +=. For Func, you’ll need to compose manually - since only the last return value is used.

Best Practices for Using Action and Func

Action and Func are powerful tools - but they should be used thoughtfully. Here are some guidelines:

  • Use Action for methods that don’t return a value.
  • Use Func for methods that return a value.
  • Prefer lambda expressions for short, inline logic.
  • Use named methods for complex or reusable logic.
  • Don’t overuse - clarity matters more than conciseness.

Use Action and Func to simplify your code - but always prioritize readability. If a lambda gets too long or hard to understand, extract it into a named method.

Summary

Action and Func are built-in delegate types that make it easy to pass behavior around in your C# programs. Action represents methods that return void, while Func represents methods that return a value. Both support multiple parameters and work seamlessly with lambda expressions.

You learned how to use Action and Func with collections, events, and higher-order functions - and how they simplify common programming tasks. These delegates are essential tools for writing clean, expressive, and modern C# code.

In the next article, we’ll explore Event Fundamentals - diving deeper into how events work in C#, how they’re declared and handled, and how they build on delegates to enable reactive programming.