Back to Blog
C#

C# Func Delegate: Syntax, Usage, and Practical Examples

c# func delegate: Learn how to use the C# Func delegate to pass methods as parameters, write concise lambdas, and build flexible, reusable code.

delegateslambda expressionshigher-order functionsfunctional programmingC# syntax
Diagram showing a C# Func delegate being passed as a parameter between two code blocks, representing method references and functional composition.

The C# Func delegate is the built-in tool for representing a method that returns a value. It lets you pass behavior as an argument, store it in a variable, or return it from another method without declaring a new delegate type. A Func delegate is a generic type: the last type parameter is the return type, and the preceding type parameters are the input types. The simplest form, Func<TResult>, takes no arguments and returns a value of type TResult.

Func<int> getNumber = () => 42; Console.WriteLine(getNumber()); // 42

When you need to accept input, add type parameters from left to right. For a method that takes an int and returns a string, the delegate signature is Func<int, string>.

Func<int, string> format = number => $"Value: {number}"; Console.WriteLine(format(5)); // Value: 5

The compiler infers the delegate type from the lambda expression, so you rarely need to write the full signature explicitly. This makes Func delegates practical for LINQ operations, event handling, and callback-based designs.

Func Delegate Syntax and Type Parameters

The System namespace defines multiple Func overloads, from Func<TResult> up to Func<T1, T2, ..., T16, TResult>. The last type argument is always the return type, and all preceding arguments are input types. This ordering is the same for every overload.

Func<int, int, int> add = (a, b) => a + b; Console.WriteLine(add(2, 3)); // 5

Because Func returns a value, it is useful for transformations, computations, and logic that needs to produce a result. Action delegates, on the other hand, return void and are used for operations that don't produce a value. The distinction matters when you choose which delegate type fits your method signature.

Delegate TypeReturnsTypical Use
Func<TResult>valuecomputation, conversion, lookup
Actionvoidoperation, side effect, mutation

If a method does not return a value, use Action instead of Func. Trying to use Func with a void method is a compile error because the delegate's return type must match.

Passing a Method Group to a Func Delegate

A Func delegate can wrap an existing method through a method group conversion. This is useful when the logic already exists as a named method and you want to pass it as a parameter.

static string ConvertToUpper(string input) => input.ToUpperInvariant(); // Method group conversion Func<string, string> transform = ConvertToUpper; Console.WriteLine(transform("hello")); // HELLO

The compiler checks that the method's signature matches the delegate's signature. If ConvertToUpper takes a string and returns a string, it fits Func<string, string>. This approach is clearer than writing a lambda that simply calls the method, and it keeps the code DRY when the same method is used in multiple places.

Using Func as a Method Parameter

A common reason to use Func is to accept a transformation or condition as a parameter. This lets the caller supply custom behavior without altering the method's implementation.

public static List<int> TransformList(List<int> source, Func<int, int> transform) { var result = new List<int>(source.Count); foreach (var item in source) { result.Add(transform(item)); } return result; }

Callers can then pass a lambda, a method group, or an inline expression:

var numbers = new List<int> { 1, 2, 3 }; var doubled = TransformList(numbers, x => x * 2); var incremented = TransformList(numbers, x => x + 1);

This pattern centralizes the iteration logic while leaving the operation open to extension. It is the same principle behind LINQ's Select and Where methods, which accept Func<TSource, TResult> and Func<TSource, bool> respectively.

Capturing Variables in Lambdas

Lambda expressions assigned to Func delegates capture variables from the enclosing scope. The captured variables are stored with the delegate instance, and their values are read at invocation time, not at creation time. This behavior is useful but can lead to subtle bugs if the variable changes before the delegate runs.

int addend = 10; Func<int, int> addWith = x => x + addend; addend = 20; Console.WriteLine(addWith(5)); // 25, not 15

Be aware of captures when using Func in loops. In older C# versions, a loop variable captured in a lambda was shared across iterations, causing all delegates to see the final value. C# 5 and later fixed this for foreach loops by creating a fresh capture per iteration, but you can still run into similar issues with for loops. If you need to capture an immutable snapshot, copy the value into a local variable inside the loop.

Func and Higher-Order Functions

A higher-order function is a function that takes another function as an argument or returns one as a result. Func enables both patterns. Returning a Func from a method allows you to create specialized functions on the fly.

static Func<int, int> CreateAdder(int amount) => x => x + amount; var addFive = CreateAdder(5); var addTen = CreateAdder(10); Console.WriteLine(addFive(3)); // 8 Console.WriteLine(addTen(3)); // 13

The returned closure retains the amount value, so each returned delegate behaves independently. This technique is handy for building reusable building blocks such as validation rules, filters, or configuration-based transformations.

Func vs Action vs Predicate

Choosing the right delegate type depends on the return type and the number of parameters. A Predicate<T> is a specialized Func that returns bool and is used to test a condition. The table below summarizes common delegate choices.

Delegate TypeSignatureReturn TypeUse Case
Func<T, TResult>T -> TResultany valuetransformation, projection
Action<T>T -> voidvoidside effect
Predicate<T>T -> boolboolfiltering, condition check

If a method returns a boolean and you only need a test, Predicate<T> communicates intent more clearly than Func<T, bool>. But Func<T, bool> works identically and is also accepted by LINQ methods like Where. Both are valid; the choice is mainly about readability.

Func for Caching and Memoization

A Func delegate is a natural fit for caching the result of an expensive operation. You can wrap a slow computation inside a Func and store the result after the first call.

class LazyValue<T> { private Func<T> _factory; private T? _value; private bool _computed; public LazyValue(Func<T> factory) => _factory = factory; public T GetValue() { if (!_computed) { _value = _factory(); _computed = true; } return _value!; } }

This is a simplified form of memoization. The function is evaluated only once, and subsequent calls return the cached value. The delegate defers the execution until first use, which is a common pattern for expensive resource initialization or complex calculations.

Performance and Allocation Considerations

Func delegates are reference types, and each lambda expression may compile to a new delegate instance. In hot paths, this can cause extra allocations. However, if the lambda does not capture any variables, the compiler can cache a static delegate instance and reuse it, eliminating repeated allocations.

For example, passing x => x * 2 to a method in a loop may create a new delegate each iteration if the compiler cannot prove it is capture-free. In practice, the JIT can often inline delegate calls, so the overhead is negligible for most applications. If you are building a high-throughput library, you can cache a delegate in a static field to avoid recreating it.

private static readonly Func<int, int> Double = x => x * 2;

Measure with a profiler before optimizing; delegate allocation is rarely the bottleneck compared to I/O or database queries.

Common Pitfalls with Func Delegates

One frequent mistake is using a Func when the method actually returns void. The compiler rejects this because Func<T> requires a return value. Another issue is expecting the delegate to execute immediately. A Func does not run until you invoke it. If you need immediate execution, call the delegate at the point of creation, not later.

Also, be careful when passing a Func to a method that may invoke it after the original context has changed. Captured variables reflect the current state at invocation time, as shown earlier. This can cause unexpected results if the captured variable is a mutable loop variable or a property that changes after the delegate is created.

When to Use a Named Delegate Instead

While Func covers most scenarios, a named delegate type can improve readability when the same signature is used across many methods. For example, in a domain model, you might define a delegate to represent a validation function:

public delegate bool ValidationRule(string input);

This gives the delegate a semantic name and can be documented. However, it requires extra code and is often unnecessary. Choose a named delegate when the signature appears in multiple places and a descriptive name adds significant clarity. Otherwise, using Func keeps the code concise and follows common .NET conventions.

Func Usage in Async and Cancellation Scenarios

Async methods that return a value can be wrapped in Func<Task<T>>. This allows deferring the async operation and controlling when it starts.

Func<Task<string>> fetchData = async () => { await Task.Delay(100); return "data"; }; string result = await fetchData();

The delegate returns a Task<string>, and the caller can decide whether to await it immediately or combine it with other operations. This pattern is useful for lazy async initialization or for passing a factory that might start after some precondition is met.

Remember that a Func delegate itself is not async. The delegate simply returns a Task; the asynchronous behavior is determined by the implementation. If the caller needs to cancel the operation, the delegate should accept a CancellationToken as one of its parameters.

Func<CancellationToken, Task<string>> fetchData = async token => { await Task.Delay(100, token); return "data"; };

Passing the token into the delegate body respects cancellation and avoids blocking the thread.

c# func delegate: Practical Usage and Code Examples | RYUSLOG DEV