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:
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:
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 aFunc<int>
that takes no parameters and returns an integer.greet
is aFunc<string, string>
that takes a string parameter and returns a string.add
is aFunc<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:
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:
// 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:
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:
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 aPredicate<T>
(which is similar to aFunc<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
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
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#:
Delegate | Description |
---|---|
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:
// 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:
-
Create a method that takes a
Func<int, int, int>
and two integers, and returns the result of applying the function to the integers. -
Write a program that uses
Func
to filter a list of strings based on different criteria (length, starting letter, etc.). -
Implement a simple calculator that uses different
Func
delegates for addition, subtraction, multiplication, and division. -
Create a method that composes two
Func<int, int>
delegates into a single function that applies them in sequence. -
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! :)