[C#] Defining Methods That Accept Lambda Expressions Using Action, Func, and Predicate

In C#, you can accept “processing logic itself” (such as lambda expressions or methods) as arguments for a method. This allows you to create flexible and reusable methods where the caller can decide a specific part of the behavior.

While it was previously necessary to define custom delegate types, in modern C# development, it is common to use the general-purpose delegates provided in the standard library (System namespace): Action, Func, and Predicate.

This article explains the role of each delegate and how to implement methods that take them as arguments.

目次

Overview of Standard Delegates (Action, Func, Predicate)

The definitions and uses of frequently used standard delegates are as follows. Using them correctly allows you to clearly express the presence or absence of return values and the intent of the code.

Delegate NameReturn ValueMeaning / Usage
ActionvoidExecutes a process with no arguments.
Action<T>voidAccepts an argument T and executes a process without returning a value (side effects only).
Func<TResult>TResultExecutes a process with no arguments and returns a result TResult.
Func<T, TResult>TResultAccepts an argument T and returns a result TResult (transformation or generation).
Predicate<T>boolAccepts an argument T and determines if it meets a condition (returns a boolean).

Note: Func and Action can take up to 16 arguments (e.g., Action<T1, T2, ...>).

1. Methods Accepting Action Delegates (Executing Processes)

Action delegates are used when passing processing that does not have a return value (such as console output, logging, or state changes).

The following code is an example of a method that loops a specified number of times and executes the process (action) passed by the caller each time.

using System;

namespace DelegateExamples
{
    class Program
    {
        static void Main()
        {
            // Usage example: Accepts an index from 0 to 2 and prints to console
            // The argument 'n' is the current loop index
            Repeat(3, n => Console.WriteLine($"This is execution number {n + 1}."));
        }

        /// <summary>
        /// Loops a specified number of times and executes an action each time.
        /// </summary>
        /// <param name="count">Number of iterations</param>
        /// <param name="action">The process to execute (argument is the current index)</param>
        static void Repeat(int count, Action<int> action)
        {
            for (var i = 0; i < count; i++)
            {
                // Execute the passed delegate (lambda expression)
                action(i);
            }
        }
    }
}

Execution Result:

This is execution number 1.
This is execution number 2.
This is execution number 3.

2. Methods Accepting Func Delegates (Generating/Converting Values)

Func delegates are used when passing processing that returns a value. They are suitable for cases where you provide a calculation formula to receive a result, or convert input to output like a factory production line.

The following code is an example of a method that accepts an index and generates a sequence of calculation results.

using System;
using System.Collections.Generic;

namespace DelegateExamples
{
    class Program
    {
        static void Main()
        {
            // Usage example: Accepts indices 0-4, calculates 2 to the power of n, and returns it
            // Treated as Func<int, double>
            var seq = Generate(5, n => Math.Pow(2, n));

            foreach (var x in seq)
            {
                Console.WriteLine(x);
            }
        }

        /// <summary>
        /// Generates a sequence using the specified generator function.
        /// </summary>
        /// <param name="count">Number of items to generate</param>
        /// <param name="generator">Function to generate a value from an index</param>
        /// <returns>The generated sequence</returns>
        static IEnumerable<T> Generate<T>(int count, Func<int, T> generator)
        {
            for (int i = 0; i < count; i++)
            {
                // Call the generator function and return the result to the caller
                yield return generator(i);
            }
        }
    }
}

Execution Result:

1
2
4
8
16

3. Methods Accepting Predicate Delegates (Condition Checks)

Predicate<T> delegates are used when passing processing that determines whether an object meets a specific condition (returning true or false). They are often used for implementing filtering or search logic.

Note: While functionally the same as Func<T, bool>, it is used to semantically emphasize “condition checking.”

The following code is an example of a method that extracts only elements matching a condition from a list of numbers to create a new list.

using System;
using System.Collections.Generic;

namespace DelegateExamples
{
    class Program
    {
        static void Main()
        {
            var numbers = new[] { 1, 5, 8, 10, 13, 20 };

            // Usage example: Pass a condition (Predicate) to extract numbers >= 10
            List<int> largeNumbers = Filter(numbers, n => n >= 10);

            Console.WriteLine("--- Numbers >= 10 ---");
            largeNumbers.ForEach(Console.WriteLine);
            
            // Usage example: Extract only even numbers
            List<int> evenNumbers = Filter(numbers, n => n % 2 == 0);
            
            Console.WriteLine("--- Even Numbers ---");
            evenNumbers.ForEach(Console.WriteLine);
        }

        /// <summary>
        /// Extracts only elements that match the condition.
        /// </summary>
        /// <param name="items">Input collection</param>
        /// <param name="predicate">Condition (keep if true)</param>
        /// <returns>Extracted list</returns>
        static List<T> Filter<T>(IEnumerable<T> items, Predicate<T> predicate)
        {
            var result = new List<T>();

            foreach (var item in items)
            {
                // Execute Predicate, add to list only if it returns true
                if (predicate(item))
                {
                    result.Add(item);
                }
            }

            return result;
        }
    }
}

Execution Result:

--- Numbers >= 10 ---
10
13
20
--- Even Numbers ---
8
10
20

Summary

By adopting delegates as method arguments, you can separate the “framework (loops or list operations)” from the “content (specific processing or conditions)” of your logic.

  • Use Action for processing with side effects.
  • Use Func for calculations or conversions that return values.
  • Use Predicate for condition checking.

By properly using these standard delegates, you can design code that is versatile and easy to read.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次