Methods as values
What you'll learn:
- store a method in a variable and pass it to another method through
Func,ActionandPredicate; - write lambda expressions and explain what a closure captures;
- filter, transform and sort items with LINQ methods and compute totals over them;
- explain when a LINQ query runs and what
ToList()is for.

Picture a recipe card. You can cook from it yourself, or you can hand the card to a friend, who cooks the dish when it is needed. The card can go into a drawer, be swapped for another one or be passed on, and the recipe never has to be rewritten.
So far a method has only been called, like the copier from the theme on methods: you wrote Max(3, 8), and it ran at once. You cannot hand a copier to a friend, but you can hand over a recipe card. In C# a method can be handed over like that card: put into a variable or given to another method, and called later.
Why is this useful? Say one place in a program has to double every number of a list, and another has to square each one. The two methods come out the same except for one line with the calculation. It is handier to write one method that walks the list and to hand it the calculation, like a recipe card.
Let's start with a variable that holds a method:
Func<int, int> f = Twice;
Console.WriteLine(f(5)); // 10
f = Square;
Console.WriteLine(f(5)); // 25
int Twice(int x) => x * 2;
int Square(int x) => x * x;Let's go through the program line by line:
Func<int, int> f = Twice;puts the methodTwiceitself into the variablef. There are no parentheses afterTwice: the method is not called here, it is handed over.f(5)calls the method thatfholds right now. That isTwice, and it returns10.f = Square;puts another method into the same variable.- The second call
f(5)now runsSquareand prints25.
A variable that holds a method
The type Func<int, int> describes a method that takes an int and returns an int. A type whose variable holds a reference to a method is called a delegate. You call a delegate variable the same way as the method itself: the name and the arguments in parentheses.
A method name without parentheses, like Twice in the first line, is called a method group. The word "group" is there because one name may cover several overloads, and the compiler picks the one that fits the delegate.
Here the recipe card and the delegate part ways. Any recipe can be written on a card, but a Func<int, int> variable takes only a method that takes an int and returns an int. The method Greet(string name) does not fit there: the compiler checks the parameters and the return type.
Ready-made delegates
.NET has ready-made delegates of three kinds. They cover almost every task, so your own delegate types, declared with the word delegate, are rarely written. Interviewers like to ask this: how do Func, Action and Predicate differ?
Func returns a result
The angle brackets list the parameter types, and the last one is the return type:
Func<int>: no parameters, returns anint;Func<int, int>: takes anint, returns anint;Func<int, int, int>: takes twoints, returns anint.
Action returns nothing
Action describes a void method. The angle brackets hold only parameter types: Action<string> takes a string and returns nothing. Action without angle brackets takes nothing.
Predicate checks a value
Predicate<T> takes one value of type T and returns a bool: whether the value fits or not. Such a check is called a predicate.
Action<string> greet = Greet;
greet("Anna"); // Hello, Anna!
Predicate<int> isEven = IsEven;
Console.WriteLine(isEven(4)); // True
Console.WriteLine(isEven(7)); // False
void Greet(string name) => Console.WriteLine($"Hello, {name}!");
bool IsEven(int n) => n % 2 == 0;greet("Anna") runs Greet and prints a greeting. isEven(4) calls IsEven, which answers true.
A method as an argument
Back to the task from the start of the lesson. A method parameter can be a delegate too, and then the method gets the calculation from outside:
List<int> nums = [1, 2, 3];
Console.WriteLine(string.Join(" ", Transform(nums, Twice))); // 2 4 6
Console.WriteLine(string.Join(" ", Transform(nums, Square))); // 1 4 9
List<int> Transform(List<int> items, Func<int, int> f)
{
List<int> result = [];
foreach (int n in items)
{
result.Add(f(n));
}
return result;
}Let's see what Transform does:
- The parameter
fof typeFunc<int, int>accepts any method frominttoint. - The loop walks the list and calls
f(n)for each item. - The results go into a new list
result, which the method returns. - The call
Transform(nums, Twice)hands overTwiceas a card, andTransform(nums, Square)hands over another card. The loop is written once.
Ready-made .NET methods take delegates too. A list has the method FindAll: you pass it a Predicate<T>, and it returns a new list of the items that fit. An example call: nums.FindAll(IsEven).
Common mistake
The method was passed with parentheses. Here Apply gets a method and a number and calls that method with the number x:
Console.WriteLine(Apply(Twice(), 5)); // ✗
int Apply(Func<int, int> f, int x) => f(x);
int Twice(int x) => x * 2;error CS7036: There is no argument given that corresponds to the required parameter 'x' of 'Twice(int)'The program does not compile. Parentheses after a name mean a call, so the compiler tries to call Twice with no arguments, and Twice needs x. To pass the method itself, write its name without parentheses: Apply(Twice, 5).
Key points
- A delegate is a type whose variable holds a reference to a method; you call it like the method itself:
f(5). Funcreturns a result, and its type comes last in the angle brackets;Actionreturns nothing;Predicate<T>answers with abool.- To pass a method, write its name without parentheses:
Transform(nums, Twice). With parentheses it is already a call.