.NET Academy

Delegates and lambdas

Treat behavior as data — store functions in variables and pass them to other methods.

12 min read

You will learn

  • Explain what a delegate is
  • Use Func, Action and Predicate
  • Write lambda expressions
  • Write methods that take functions as parameters

Functions as values

So far, variables have held data: numbers, strings, objects. C# also lets a variable hold a function. That one idea unlocks sorting by any rule, filtering with any condition, event handlers, LINQ, ASP.NET Core's endpoint mapping and much more.

Func<int, int> square = x => x * x;
 
Console.WriteLine(square(5));       // 25 — call it like a method

Delegates

A delegate is a type that describes a method signature — its parameters and return type. A variable of a delegate type can hold any method (or lambda) with that signature.

You rarely declare your own delegate types, because .NET provides generic ones for almost every shape:

DelegateShapeExample
Func<TResult>no parameters → returns a valueFunc<DateTime> now = () => DateTime.Now;
Func<T, TResult>one parameter → returns a valueFunc<string, int> len = s => s.Length;
Func<T1, T2, TResult>two parameters → returns a valueFunc<int, int, int> add = (a, b) => a + b;
Actionno parameters, returns nothingAction beep = () => Console.Beep();
Action<T>one parameter, returns nothingAction<string> log = msg => Console.WriteLine(msg);
Predicate<T>one parameter → boolPredicate<int> isEven = n => n % 2 == 0;

The last type argument of Func is the return type.

Lambda expressions

A lambda is an anonymous, inline function: parameters => body.

x => x * 2                         // one parameter, expression body
(a, b) => a + b                    // several parameters
() => Console.WriteLine("hi")      // no parameters
s =>                               // statement body for more logic
{
    var trimmed = s.Trim();
    return trimmed.ToUpper();
}
(int x, int y) => x * y            // explicit parameter types (usually inferred)

Methods with a matching signature work too:

bool IsLong(string s) => s.Length > 5;
 
Func<string, bool> check = IsLong;     // a method group — no parentheses!

Passing functions to methods

This is where delegates shine. Write the general algorithm once, and let the caller supply the specific behavior:

List<T> Where<T>(List<T> items, Func<T, bool> predicate)
{
    var result = new List<T>();
    foreach (var item in items)
        if (predicate(item)) result.Add(item);
    return result;
}
 
var numbers = new List<int> { 1, 5, 12, 7, 20 };
var big = Where(numbers, n => n > 6);              // 12, 7, 20
var even = Where(numbers, n => n % 2 == 0);        // 12, 20

One method, endless conditions. You just wrote a tiny version of LINQ's Where.

You've already used this pattern without knowing it:

list.RemoveAll(x => x < 0);
list.Sort((a, b) => a.Name.CompareTo(b.Name));
Array.Find(people, p => p.Age > 30);

Returning functions

A method can also create and return a function:

Func<int, int> Adder(int amount) => x => x + amount;
 
var addTen = Adder(10);
Console.WriteLine(addTen(5));      // 15

addTen remembers amount = 10 even after Adder has returned. That's a closure — the topic of the next lesson.

Under the hood

A delegate is an object: it stores a reference to the target method (and, for instance methods, the target object). That's why you can put them in variables, lists and dictionaries — e.g. a Dictionary<string, Func<double, double, double>> mapping "+" to an addition lambda.

Try it

Program.cs
Loading editor…

C# runtime not loaded

Key takeaways

  • Delegates let variables and parameters hold functions.
  • Use Func<..., TResult> for functions returning a value, Action<...> for void ones.
  • Lambdas (x => x * 2) are concise inline functions; method names work as delegates too.
  • Passing functions as parameters lets you write general algorithms with pluggable behavior.

Exercise

Transform applies a function to every element and returns the results. Filter keeps only the items for which keep returns true (it's generic, so it works with any type). Compose returns a new function that applies f first, then g.

Program.cs
Loading editor…

C# runtime not loaded

Hint 1

Transform: create a result array of the same length and set result[i] = transform(values[i]). A Func is called just like a method.

Hint 2

Filter: create a new List<T> and Add each item where keep(item) is true.

Hint 3

Compose returns a lambda: return x => g(f(x));

Show solution
var tens = Transform(new[] { 1, 2, 3 }, x => x * 10);
Console.WriteLine(string.Join(", ", tens));

int[] Transform(int[] values, Func<int, int> transform)
{
    var result = new int[values.Length];
    for (int i = 0; i < values.Length; i++)
    {
        result[i] = transform(values[i]);
    }
    return result;
}

List<T> Filter<T>(List<T> items, Func<T, bool> keep)
{
    var result = new List<T>();
    foreach (var item in items)
    {
        if (keep(item)) result.Add(item);
    }
    return result;
}

Func<int, int> Compose(Func<int, int> f, Func<int, int> g) => x => g(f(x));

Quiz

4 questions · 3 correct to pass
  1. 1.What does `Func<int, string>` represent?

  2. 2.Which delegate type fits a method that takes a string and returns nothing?

  3. 3.What is `x => x * x`?

  4. 4.Given `Func<int, int> twice = n => n * 2;`, what is `twice(twice(3))`?

To complete this lesson

  • ○ Solve the exercise
  • ○ Pass the quiz (3/4 correct)