Skip to main content

C# Func Delegates

Introduction

Func delegates are a powerful feature in C# that allow you to work with methods as objects. They provide a clean, flexible way to represent methods that return a value. If you're coming from a background in other programming languages, you might think of them as function pointers or callbacks.

In this tutorial, we'll explore what Func delegates are, how they work, and how you can use them in your C# applications. By the end, you'll have a solid understanding of how to leverage this feature to write more concise and flexible code.

What are Func Delegates?

A Func delegate is a built-in generic delegate in C# that represents a method that takes up to 16 input parameters and returns a value. The last type parameter in a Func delegate always represents the return type.

The syntax for a Func delegate is:

csharp
Func<T1, T2, ..., TResult>

Where:

  • T1, T2, etc. are the input parameter types (optional)
  • TResult is the return type (required)

Basic Usage of Func Delegates

Let's start with a simple example of using a Func delegate:

csharp
using System;

class Program
{
static void Main()
{
// Func with no parameters that returns an int
Func<int> getRandomNumber = () => new Random().Next(1, 100);

// Func with one parameter that returns a string
Func<string, string> greet = (name) => $"Hello, {name}!";

// Func with two parameters that returns an int
Func<int, int, int> add = (a, b) => a + b;

// Using the Func delegates
Console.WriteLine($"Random number: {getRandomNumber()}");
Console.WriteLine(greet("John"));
Console.WriteLine($"5 + 3 = {add(5, 3)}");
}
}

Output:

Random number: 42
Hello, John!
5 + 3 = 8

In this example:

  • getRandomNumber is a Func<int> that takes no parameters and returns an integer.
  • greet is a Func<string, string> that takes a string parameter and returns a string.
  • add is a Func<int, int, int> that takes two integer parameters and returns an integer.

Creating Func Delegates

There are several ways to create Func delegates:

1. Using Lambda Expressions

Lambda expressions provide a concise way to create delegates:

csharp
Func<int, bool> isEven = x => x % 2 == 0;
Console.WriteLine($"Is 4 even? {isEven(4)}"); // Output: Is 4 even? True
Console.WriteLine($"Is 7 even? {isEven(7)}"); // Output: Is 7 even? False

2. Using Method References

You can assign existing methods to Func delegates:

csharp
// Define a method
static double CalculateCircleArea(double radius)
{
return Math.PI * radius * radius;
}

// Create a Func delegate that references the method
Func<double, double> calculateArea = CalculateCircleArea;

// Use the delegate
Console.WriteLine($"Area of circle with radius 5: {calculateArea(5):F2}");
// Output: Area of circle with radius 5: 78.54

3. Using Anonymous Methods

Although less common in modern C# code, you can use anonymous methods:

csharp
Func<int, int, int> multiply = delegate(int x, int y) { return x * y; };
Console.WriteLine($"4 × 5 = {multiply(4, 5)}"); // Output: 4 × 5 = 20

Passing Func Delegates as Parameters

One powerful feature of delegates is the ability to pass them as parameters to other methods:

csharp
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Using a Func delegate with List.FindAll
List<int> evenNumbers = numbers.FindAll(IsEven);
Console.WriteLine("Even numbers:");
foreach (var num in evenNumbers)
{
Console.Write($"{num} ");
}

// Using a lambda expression directly
List<int> oddNumbers = numbers.FindAll(x => x % 2 != 0);
Console.WriteLine("\nOdd numbers:");
foreach (var num in oddNumbers)
{
Console.Write($"{num} ");
}
}

static bool IsEven(int number)
{
return number % 2 == 0;
}
}

Output:

Even numbers:
2 4 6 8 10
Odd numbers:
1 3 5 7 9

In this example:

  • IsEven is a method that returns a boolean.
  • FindAll expects a Predicate<T> (which is similar to a Func<T, bool>).
  • We use both a method reference and a lambda expression.

Real-World Examples

Let's look at some practical applications of Func delegates:

Example 1: Custom Sorting

csharp
using System;
using System.Collections.Generic;

class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

class Program
{
static void Main()
{
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 },
new Person { Name = "Charlie", Age = 35 },
new Person { Name = "Diana", Age = 28 }
};

// Sort people by different criteria using Func delegates
Console.WriteLine("People sorted by name:");
SortAndPrint(people, p => p.Name);

Console.WriteLine("\nPeople sorted by age:");
SortAndPrint(people, p => p.Age);
}

static void SortAndPrint<T, TKey>(List<T> items, Func<T, TKey> keySelector)
where TKey : IComparable<TKey>
{
// Create a sorted copy using the keySelector Func
var sortedItems = new List<T>(items);
sortedItems.Sort((a, b) => keySelector(a).CompareTo(keySelector(b)));

// Print the sorted items
foreach (var item in sortedItems)
{
if (item is Person person)
{
Console.WriteLine($"{person.Name}, {person.Age} years old");
}
}
}
}

Output:

People sorted by name:
Alice, 30 years old
Bob, 25 years old
Charlie, 35 years old
Diana, 28 years old

People sorted by age:
Bob, 25 years old
Diana, 28 years old
Alice, 30 years old
Charlie, 35 years old

Example 2: Data Processing Pipeline

csharp
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
// Create a processing pipeline using Func delegates
Func<int, int> addFive = x => x + 5;
Func<int, int> multiplyByTwo = x => x * 2;
Func<int, bool> isGreaterThanTwenty = x => x > 20;

// Process a list of numbers through the pipeline
List<int> numbers = new List<int> { 5, 10, 15, 20 };

Console.WriteLine("Original numbers:");
PrintNumbers(numbers);

// Apply transformations
var transformedNumbers = Transform(numbers, addFive);
Console.WriteLine("\nAfter adding 5:");
PrintNumbers(transformedNumbers);

transformedNumbers = Transform(transformedNumbers, multiplyByTwo);
Console.WriteLine("\nAfter multiplying by 2:");
PrintNumbers(transformedNumbers);

// Filter results
var filteredNumbers = Filter(transformedNumbers, isGreaterThanTwenty);
Console.WriteLine("\nNumbers greater than 20:");
PrintNumbers(filteredNumbers);
}

static List<int> Transform(List<int> numbers, Func<int, int> transformer)
{
List<int> result = new List<int>();
foreach (var number in numbers)
{
result.Add(transformer(number));
}
return result;
}

static List<int> Filter(List<int> numbers, Func<int, bool> predicate)
{
List<int> result = new List<int>();
foreach (var number in numbers)
{
if (predicate(number))
{
result.Add(number);
}
}
return result;
}

static void PrintNumbers(List<int> numbers)
{
foreach (var number in numbers)
{
Console.Write($"{number} ");
}
Console.WriteLine();
}
}

Output:

Original numbers:
5 10 15 20

After adding 5:
10 15 20 25

After multiplying by 2:
20 30 40 50

Numbers greater than 20:
30 40 50

Func vs. Action vs. Predicate

It's important to understand the differences between common delegate types in C#:

DelegateDescription
Func<..., TResult>Takes 0-16 parameters and returns a value of type TResult
Action<...>Takes 0-16 parameters and doesn't return a value (void)
Predicate<T>Takes one parameter of type T and returns a boolean (equivalent to Func<T, bool>)

Here's a quick example showing all three:

csharp
// Func - returns a value
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5); // result = 8

// Action - doesn't return a value
Action<string> printMessage = message => Console.WriteLine(message);
printMessage("Hello, World!"); // Output: Hello, World!

// Predicate - returns a boolean
Predicate<int> isPositive = num => num > 0;
bool check = isPositive(5); // check = true

Summary

Func delegates are a powerful feature in C# that enable you to:

  • Treat methods as first-class objects
  • Pass methods as arguments to other methods
  • Store methods in variables
  • Create and use anonymous functions
  • Build flexible and reusable code structures

They are particularly useful in scenarios involving:

  • Collection manipulation (filtering, mapping, etc.)
  • Event handling
  • Callbacks
  • Strategy patterns
  • Lazy evaluation

By mastering Func delegates, you'll be able to write more concise, modular, and flexible code in C#.

Exercises

To solidify your understanding of Func delegates, try these exercises:

  1. Create a method that takes a Func<int, int, int> and two integers, and returns the result of applying the function to the integers.

  2. Write a program that uses Func to filter a list of strings based on different criteria (length, starting letter, etc.).

  3. Implement a simple calculator that uses different Func delegates for addition, subtraction, multiplication, and division.

  4. Create a method that composes two Func<int, int> delegates into a single function that applies them in sequence.

  5. Build a more complex data processing pipeline that transforms a list of objects using multiple Func delegates.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)