C# - Func Delegate

The Func delegate is a built-in generic delegate type in C#. It's primarily used to represent methods that have a return value. The Func delegate can have zero to sixteen input parameters, and it always has one return type.

The type arguments for the Func delegate are the input parameter types followed by the return type. For instance, Func<int, bool> represents a method that takes an int as a parameter and returns a bool.


using System;

namespace FuncDelegateExample
{
    class Program
    {
        static void Main()
        {
            // Using Func delegate to point to a method that takes an int and returns a bool
            Func<int, bool> isEven = CheckIfEven;

            // Invoking the delegate
            bool result = isEven(10);  // Should return true
            Console.WriteLine($"Is 10 even? {result}");

            // Using lambda expression with Func
            Func<int, int, int> add = (x, y) => x + y;

            int sum = add(5, 3);  // Should return 8
            Console.WriteLine($"5 + 3 = {sum}");
        }

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

The output of the program will be:


Is 10 even? True
5 + 3 = 8
  1. The line Func<int, bool> isEven = CheckIfEven; creates a Func delegate that points to the CheckIfEven method. This method takes an integer argument and returns a boolean.
  2. The line bool result = isEven(10); invokes the delegate with the argument 10, checks if it is even, and stores the result (True) in result.
  3. The line Console.WriteLine($"Is 10 even? {result}"); prints "Is 10 even? True" to the console.
  4. The line Func<int, int, int> add = (x, y) => x + y; uses a lambda expression to create another Func delegate for addition.
  5. The line int sum = add(5, 3); invokes this delegate with the arguments 5 and 3, performs the addition, and stores the result (8) in sum.
  6. The line Console.WriteLine($"5 + 3 = {sum}"); prints "5 + 3 = 8" to the console.

Remember, the last type parameter of the Func delegate always indicates the return type. In the isEven delegate, for example, the int indicates the input type and bool indicates the return type.

Func Delegate Real-time Example:

One common real-world scenario where Func delegates shine is in data filtering and transformations, especially with databases and collections. Let's take an example from the world of e-commerce.

Scenario:

Suppose you're building an e-commerce platform and you want to provide a feature where users can filter products based on their preferences. The filter criteria can be anything: price range, product rating, brand, etc.

Using Func delegates, you can create dynamic filter criteria without having to write a separate method for each type of filter.


using System;
using System.Collections.Generic;
using System.Linq;

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public float Rating { get; set; }
    public string Brand { get; set; }
}

public class ProductFilter
{
    public List<Product> FilterByCriteria(List<Product> products, Func<Product, bool> criteria)
    {
        return products.Where(criteria).ToList();
    }
}

public class Program
{
    public static void Main()
    {
        List<Product> products = new List<Product>
        {
            new Product { Id = 1, Name = "Laptop", Price = 1000m, Rating = 4.5f, Brand = "BrandA" },
            new Product { Id = 2, Name = "Mobile", Price = 500m, Rating = 4.2f, Brand = "BrandB" },
            new Product { Id = 3, Name = "Headphones", Price = 100m, Rating = 4.8f, Brand = "BrandA" },
            // ... other products
        };

        var filter = new ProductFilter();

        // Filter products by price
        var expensiveProducts = filter.FilterByCriteria(products, p => p.Price > 500m);
        Console.WriteLine($"Expensive Products (Price > 500): {expensiveProducts.Count}");

        // Filter products by rating
        var highlyRatedProducts = filter.FilterByCriteria(products, p => p.Rating > 4.5f);
        Console.WriteLine($"Highly Rated Products (Rating > 4.5): {highlyRatedProducts.Count}");

        // Filter products by brand
        var brandAProducts = filter.FilterByCriteria(products, p => p.Brand == "BrandA");
        Console.WriteLine($"BrandA Products: {brandAProducts.Count}");
    }
}

In the example above:

  • We have a Product class representing products in the e-commerce platform.
  • The ProductFilter class contains a FilterByCriteria method, which filters a list of products based on a Func delegate.
  • In the Main method, we have a list of products and we use the FilterByCriteria method to filter products by different criteria: price, rating, and brand.

The output of the program will be:


Expensive Products (Price > 500): 1
Highly Rated Products (Rating > 4.5): 1
BrandA Products: 2

Explanation:

  1. A list of Product objects is created with three items.
  2. An instance of the ProductFilter class named filter is created.
  3. The FilterByCriteria method is called three times with different filtering criteria:
    • First, it filters products with a price greater than 500. The count of such products is 1, which includes the "Laptop".
    • Second, it filters products with a rating greater than 4.5. The count of such products is 1, which includes the "Headphones".
    • Third, it filters products with the brand "BrandA". The count of such products is 2, which includes "Laptop" and "Headphones".

By using Func delegates, we can easily extend our filtering capabilities without having to modify the ProductFilter class or add more methods for different filter criteria. The filtering logic is abstracted, making it reusable and maintainable.

Points to Remember:
  1. Syntax: Func is a generic delegate type defined in the System namespace.
  2. Return Type: Unlike Action, Func always returns a value. The last type parameter specifies the return type.
  3. Type Parameters: Func can take up to 16 input parameters (as of C# 9.0).
  4. Lambda Expressions: It is often used with lambda expressions for inline method definitions.
  5. Method Group Conversion: You can also assign existing methods to a Func delegate as long as the method signature matches the delegate signature.
  6. LINQ: It is commonly used in LINQ queries for filtering, projection, etc.
  7. Strongly-Typed: Being a generic delegate, Func is strongly-typed, ensuring type safety.
  8. Null Handling: Always check for null before invoking a Func delegate, or use the null conditional operator (?.).
  9. Multi-threading: Consider thread-safety when using Func delegates in multi-threaded applications.
  10. Higher-Order Functions: Func delegates can be passed as arguments to methods or returned from methods, making it useful for higher-order functions.
  11. Immutability: Like all delegates, Func delegates are immutable. When you combine or remove delegates, a new delegate instance is created.
  12. Common Usage: Used extensively for callbacks, predicates, or functionalities that need to be passed around as parameters or stored as variables.

Remember, while Func and Action delegates are convenient, they might not be as self-explanatory as custom delegate types, which could impact code readability. Always weigh the pros and cons when deciding to use them.