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 returnsvoid
.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.