Action's counterpart for methods that hand something back — the delegate type behind almost every LINQ method you're about to learn.
Action covers "do something, return nothing." But what about "compute something and give me the answer"? A price calculator, a validator that returns true/false, a transformer that turns a string into an int — all of these need to hand a result back to whoever called them.
That's exactly what Func is for. If Action is "do this," Func is "compute this and give it back to me." Together, they cover almost every delegate shape you'll write in day-to-day C#.
Func is a built-in generic delegate type representing "a method that takes some arguments and returns a value." Like Action, it comes in multiple generic flavors — but every Func variant's last type parameter is always the return type.
public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T arg);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
// ...continues up to Func<T1, ..., T16, TResult>
Read Func<T1, T2, TResult> as: "takes a T1 and a T2, returns a TResult." The very last type parameter is always the return type — everything before it is a parameter, in order.
voidAction<string> — one parameter, no return valueFunc<string, int> — one parameter, returns intAction is always void. There's no way to squeeze a return value out of it — the delegate type itself forbids it at compile time. As soon as you need to pass in "logic that computes something and hands it back" (a comparison, a transformation, a validation check), Action simply doesn't fit.
Func mirrors Action's design (generic, multiple arities, no new type declarations needed) but adds exactly one thing: a mandatory return type as the final type parameter. Between Action and Func, the BCL covers essentially every "pass behavior as data" scenario you'll encounter — which is precisely why LINQ, which you'll study right after this module, is built almost entirely on Func delegates (Where takes a Func<T, bool>, Select takes a Func<T, TResult>, and so on).
Once you're comfortable reading Func<T1, T2, TResult> at a glance, you're already halfway to reading LINQ method signatures fluently. Func is the single most important delegate type in modern, functional-style C#.
Func<string, int, bool>
bool is what comes backstring and int, in order, are the parametersbool SomeMethod(string s, int n)
using System;
class Program
{
static int Square(int x) => x * x;
static bool IsPositive(int x) => x > 0;
static int Add(int a, int b) => a + b;
static void Main()
{
Func square = Square;
Func isPositive = IsPositive;
Func add = Add;
Console.WriteLine(square(5)); // 25
Console.WriteLine(isPositive(-3)); // False
Console.WriteLine(add(4, 6)); // 10
// Passing a Func into another method
PrintResult(square, 7); // "Result: 49"
PrintResult(isPositive, -1); // "Result: False" (bool prints fine as object here via generics)
}
static void PrintResult(Func operation, int input)
{
T result = operation(input);
Console.WriteLine($"Result: {result}");
}
}
Code → Meaning → Result: PrintResult is generic over the return type of the Func it receives — it doesn't care whether the operation returns an int or a bool, only that it's a Func<int, T> for some T. This kind of generic, behavior-accepting method is exactly the shape LINQ methods take.
A validation pipeline that runs a list of independent business rules against an order, each expressed as a Func<Order, bool>, and reports every rule that failed:
using System;
using System.Collections.Generic;
public record Order(decimal Total, string CustomerEmail, int ItemCount);
public class OrderValidator
{
private readonly List<(string RuleName, Func IsValid)> _rules = new();
public void AddRule(string name, Func rule) => _rules.Add((name, rule));
public List Validate(Order order)
{
var failures = new List();
foreach (var (name, isValid) in _rules)
{
if (!isValid(order))
failures.Add($"Failed rule: {name}");
}
return failures;
}
}
class Program
{
static void Main()
{
var validator = new OrderValidator();
validator.AddRule("Total must be positive", o => o.Total > 0);
validator.AddRule("Must have at least one item", o => o.ItemCount > 0);
validator.AddRule("Email must look valid", o => o.CustomerEmail.Contains('@'));
var badOrder = new Order(Total: -5m, CustomerEmail: "not-an-email", ItemCount: 0);
var failures = validator.Validate(badOrder);
foreach (var failure in failures)
Console.WriteLine(failure);
// Failed rule: Total must be positive
// Failed rule: Must have at least one item
// Failed rule: Email must look valid
}
}
OrderValidator never needs to change when a new rule is added — new rules are just new Func<Order, bool> values registered with AddRule. This is a small preview of the "pipeline of predicates" pattern you'll use constantly once you reach Where in LINQ.
Action is like a mail slot — you put something in, nothing comes back out. Func is like a vending machine — you put something in (money, a selection), and it hands you something back (a snack). The "last type parameter is the return type" rule is just the label on the machine telling you what you'll get.
Just like Action, Func is an ordinary MulticastDelegate generated once and instantiated per closed generic type at JIT time. One detail worth knowing: Func delegates can be multicast (chained with +=) just like Action, but as covered in lesson 097, invoking a multicast non-void delegate only surfaces the last subscriber's return value — every earlier subscriber still runs, but its result is silently discarded. In practice, most Func usage is single-subscriber precisely to avoid this trap.
Always the last one — no exceptions. Func<int> (one type parameter) takes no parameters and returns int. Func<int, int> (two type parameters) takes one int parameter and returns an int. This trips up beginners because both look similar at a glance.
A regular method that returns a value is not itself a Func — it only becomes a Func value when you assign or pass it as a method group (Func<int,int> f = Square;) or write it as a lambda. Declaring a method with a return type doesn't automatically make it usable as a delegate — you still need to reference it that way explicitly.
Writing Func<int, string, bool> when you meant "takes a string, returns a bool" — that signature actually means "takes an int and a string, returns a bool."
Count backward from the end: the last type parameter is the return type; everything before it, left to right, is the parameter list in order.
Chaining several handlers onto one Func<T, TResult> with += and assuming the invocation somehow aggregates all their results.
Keep Func single-subscriber for value-returning scenarios; use a plain List<Func<...>> (as in the validator example above) when you genuinely need to run several and inspect every result.
Func almost certainly already fits.Func<int, Order> to build orders lazily.Func<T, bool> is so common — checking whether something matches a condition — that .NET gives that specific shape its own name too: Predicate<T>. That's the next lesson.
Func<T1, T2, ..., TResult>Func<T1, ..., TResult> is a built-in generic delegate family, just like Action, but it always returns a value — the last type parameter names the return type.Func is a MulticastDelegate too, but chaining several subscribers on a Func only surfaces the last one's result — a good reason to keep Func single-subscriber.Func is the workhorse behind almost every LINQ method — mastering it here pays off immediately in the next module.You've learned how to read and use Func. Let's confirm it clicked.
1. In Func<string, int, bool>, what is the return type?
Correct: C
Why C is correct: The last type parameter of a Func is always the return type. Here, bool is last, so it's what the method returns; string and int are the two parameters, in that order.
Why A and B are incorrect: Those are parameter types, not the return type — position matters, and only the final type parameter is the return type.
Why D is incorrect: Every Func variant returns a value by definition; if you needed no return value, you'd use Action instead.
Reinforcement: Always read the last type parameter first when parsing a Func signature.
2. You need a delegate matching decimal CalculateTotal(decimal price, int quantity). Which is correct?
Correct: B
Why B is correct: The method takes a decimal then an int, and returns a decimal. Func<decimal, int, decimal> matches that exactly: parameters in order, return type last.
Why A is incorrect: Action returns void; this method returns decimal, so Action can never match it.
Why C is incorrect: That puts the return type (int) last, but the method actually returns decimal, not int.
Why D is incorrect: The parameter order is reversed — price (decimal) comes before quantity (int) in the method, not the other way around.
Reinforcement: Match parameter order exactly, and confirm the true return type belongs in the final slot.
3. Why does the order-validation example (registering rules as Func<Order, bool> in a list) scale better than hardcoding each rule inside Validate?
Correct: B
Why B is correct: Because each rule is just a value passed to AddRule, adding a new rule never requires touching OrderValidator's source — a direct example of decoupling "what to check" from "how to run checks."
Why A is incorrect: There's no special performance benefit here — the point is flexibility and decoupling, not speed.
Why C is incorrect: Each rule is still a method or lambda that returns bool; Func<Order, bool> is just how it's referenced, not a replacement for writing the logic.
Why D is incorrect: Func<Order, bool> only describes a signature — it doesn't inspect or validate anything on its own; the logic inside the lambda does that.
Reinforcement: The reusability win comes from treating each rule as data (a value in a list), not from any special power of Func itself.
You can now read and write Func signatures confidently. Next: Predicate<T>, a specialized shape you'll recognize instantly now that you understand Func<T, bool>.
dotnetmadeeasy.com — Learn C# and .NET, the right way.