Func and Action are generic delegate types that .NET declares for you, so you can store and pass functions without declaring a delegate type first. Func returns a value; Action returns nothing.
Output:
4
46.00
False
Hello!
Mia scored 87
The rule to remember: in a Func, the last type argument is the return type. Func<int, int, bool> takes two ints and returns a bool. Func<int> takes nothing and returns an int. An Action's type arguments are all parameters.
The Family
| Type | Parameters | Returns |
|---|---|---|
Action | none | void |
Action<T> | T | void |
Action<T1, T2> ... up to 16 | T1, T2, ... | void |
Func<TResult> | none | TResult |
Func<T, TResult> | T | TResult |
Func<T1, T2, TResult> ... up to 16 | T1, T2, ... | TResult |
Predicate<T> | T | bool |
Comparison<T> | T, T | int (negative, zero, positive) |
Converter<TIn, TOut> | TIn | TOut |
Predicate, Comparison and Converter are older and appear in List<T> and Array methods: FindAll(Predicate<T>), Sort(Comparison<T>), ConvertAll(Converter<T, TOut>). LINQ uses Func throughout, which is why list.Where(...) and list.FindAll(...) take lambdas of the same shape but different types:
Output:
17, 15
34, 52, 28
52, 34, 28, 17, 15
52y 34y 28y 17y 15y
Passing Functions to Methods
A Func or Action parameter lets the caller supply the part of an algorithm that varies. Two common shapes are "run this and measure it" and "try this until it works":
Output:
attempt 1 failed
attempt 2 failed
<html>ok</html>
clicks: 2
Retry is generic, so its Func<int, T> can return anything; the compiler inferred T as string from the lambda. The lambda passed to Twice changes the local clicks, which works because a lambda captures the variable itself, not a copy of its value.
Method Groups: Passing a Method by Name
You do not need a lambda to fill a Func or Action. The name of an existing method with a matching signature (a "method group") converts directly:
Output:
parsed: 43
sat is weekend: True
total quantity: 20
Select(int.Parse) is the same as Select(s => int.Parse(s)), minus one layer of call. The compiler picks the overload that fits the target type: int.Parse has several overloads, and only Parse(string) matches Func<string, int>. When the compiler cannot settle on one (several overloads fit equally well, or a generic method's type arguments cannot be inferred from a method group), the call does not compile, and a lambda that makes the call explicit fixes it.
A method group used as a Func still runs later, whenever the delegate is invoked, exactly like a lambda. The difference is only in how it is written.
Returning Functions
A method can build and return a function. The returned Func carries the values it was built from:
Output:
140
180
162
Twice(staff) builds a new function that applies the staff discount twice: 200, then 180, then 162.
A Dictionary of Functions
Storing functions in a dictionary turns a long switch into a lookup table. It is the usual shape for command handlers, calculators and menu actions, and new entries can be added at run time:
Output:
12 + 30 = 42
7 * 6 = 42
9 / 0 = cannot divide by zero
2 ^ 3 = unknown operator
Hello, Sam
DONE
Adding an operator is one more dictionary entry, with no change to the loop. The StringComparer.OrdinalIgnoreCase passed to the second dictionary makes command names case-insensitive.
Async Functions: Func<Task>, Not Action
With async code, the delegate type decides whether the caller can wait for the work:
Func<Task> save = async () => await File.WriteAllTextAsync("a.txt", "data");
await save(); // the caller can await it and see its exceptions
Func<int, Task<string>> load = async id => await FetchUserAsync(id);
string user = await load(42);
Action bad = async () => await File.WriteAllTextAsync("a.txt", "data");
bad(); // async void: fire and forget, exceptions escape
An async lambda assigned to an Action becomes an async void method: nothing can await it, and an exception inside it never reaches the caller. It is rethrown on the thread pool (or the UI thread in a desktop app), where it usually crashes the process. When you write a method that accepts async work, take a Func<Task> (or Func<Task<T>>).
When to Declare Your Own Delegate
Func and Action cover most needs. Declare a named delegate type when:
- The signature has
ref,outorinparameters.Funccannot express them, so aTryParse-style function needsdelegate bool TryParser<T>(string text, out T value);. - The name documents a role in a public API.
delegate bool Validator(string input)in a method signature says more thanFunc<string, bool>, and its parameter names appear in IntelliSense. - You need a
paramsparameter, which generic delegates also cannot have.
Events are the other place custom delegate types still appear, although the built-in EventHandler<TEventArgs> covers most of them. The delegates page covers declaring and combining delegate types.
Frequently Asked Questions
What is the difference between Func and Action in C#?
Func returns a value and Action does not. In Func<int, string, bool>, the last type argument (bool) is the return type and the others are parameters. Action<int, string> takes an int and a string and returns void. Both come in versions with 0 to 16 parameters.
How do I pass a function as a parameter in C#?
Declare the parameter as a Func or Action of the right shape and call it inside the method: static decimal Apply(decimal price, Func<decimal, decimal> rule) => rule(price);. Callers pass a lambda (Apply(80m, p => p * 0.9m)) or a method name (Apply(80m, HalfPrice)).
What is Predicate<T> in C#?
A delegate type that takes a T and returns bool, the same shape as Func<T, bool>. It predates Func and is used by List<T>.Find, FindAll, RemoveAll, Exists and Array.Find. The two types do not convert into each other, but a lambda converts to either.
How do I use Func with async code?
Use Func<Task> for an async function with no result and Func<T, Task<TResult>> for one with a result, so the caller can await it. Avoid assigning an async lambda to an Action: that makes it async void, which cannot be awaited and whose exceptions cannot be caught by the caller.
Can Func have out or ref parameters?
No. The generic parameters of Func and Action are ordinary value parameters, so there is no way to write Func<string, out int, bool>. For a signature like TryParse, declare a custom delegate: delegate bool TryParser<T>(string text, out T value);.