Func 과 Action

황현중·2025년 11월 27일

C#

목록 보기
13/24

이 글에서는 델리게이트 복습 → Func → Action → 기존 delegate와 비교 → LINQ에서의 사용 순서로 정리해본다.


1. 먼저 delegate 개념 한 줄 복습

Func와 Action을 이해하려면, 일단 델리게이트(delegate)를 알고 있어야 한다.

델리게이트는 이렇게 요약할 수 있다:

delegate = 메서드를 가리키는 변수 타입

예를 들어,

delegate bool NumberTest(int n);
// int 하나 받아서 bool(true/false)을 반환하는 메서드를 가리킬 수 있는 타입

이렇게 쓰면 된다.

static bool IsEven(int n)   { return n % 2 == 0; }
static bool IsOdd(int n)    { return n % 2 != 0; }

static int Count(int[] numbers, NumberTest test)
{
    int count = 0;
    foreach (int n in numbers)
    {
        if (test(n))
            count++;
    }
    return count;
}

int[] arr = { 3, 5, 4, 2, 6, 7, 8 };

int evenCount = Count(arr, IsEven);
int oddCount  = Count(arr, IsOdd);

여기까지가 “사용자 정의 delegate 타입(NumberTest)을 만들어서 쓰는 패턴”이다. 이제 같은 걸 더 일반적인 타입 Func / Action으로 바꿔볼 수 있다.


2. Func 한 줄 정의

Func<...> = 값을 반환하는 델리게이트를 표현하는 제네릭 타입이다.

  • 마지막 타입 매개변수 = 반환형
  • 앞의 타입들 = 매개변수 타입

예를 들어:

Func<int> f1;
// 매개변수 없음, int를 반환

Func<int, int> f2;
// int 하나를 받아서 int 반환

Func<int, int, int> f3;
// int, int 두 개를 받아서 int 반환

Func<string, int> f4;
// string 하나를 받아서 int 반환

예전에 이렇게 만들었던 델리게이트:

delegate bool NumberTest(int n);

이건 Func로 바꾸면 이렇게 표현할 수 있다:

Func<int, bool> test;
// int를 입력으로 받고, bool을 반환하는 함수

즉, delegate bool NumberTest(int n);Func<int, bool>
의미적으로 같은 역할을 하는 셈이다.

2-1. Func 간단 예제

using System;

class Program
{
    static void Main()
    {
        // int 두 개를 받아서 int를 반환하는 함수: 더하기
        Func<int, int, int> add = (a, b) => a + b;
        int r1 = add(3, 5);  // 8

        // string 하나 받아서 길이를 반환하는 함수
        Func<string, int> len = s => s.Length;
        int r2 = len("Hello");  // 5

        Console.WriteLine(r1);
        Console.WriteLine(r2);
    }
}

예전에 따로 delegate int Calc(int a, int b);를 만들고 Calc calc = Add; 이런 식으로 쓰던 걸, 이제는 그냥 Func<int, int, int>로 표현한 것뿐이다.


3. Action 한 줄 정의

Action<...> = 반환값이 없는(void) 델리게이트를 표현하는 제네릭 타입이다.

  • 타입 매개변수들 = 매개변수 타입
  • 반환형은 항상 void

예를 들면:

Action a1;
// 매개변수 없음, void 메서드

Action<string> a2;
// string 하나를 받고 void

Action<int, string> a3;
// int, string을 받고 void

3-1. Action 간단 예제

using System;

class Program
{
    static void Main()
    {
        // 매개변수 없고, 그냥 메시지만 출력
        Action hello = () =>
        {
            Console.WriteLine("Hello Action!");
        };

        // string 하나 받아서 콘솔에 출력만 하는 함수
        Action<string> print = msg =>
        {
            Console.WriteLine("메시지: " + msg);
        };

        hello();
        print("안녕");
    }
}

여기서 hello, print는 둘 다 “호출 가능한 변수”라고 보면 된다.
hello()도 함수 호출, print("안녕")도 함수 호출이다.


4. 우리가 만든 delegate ↔ Func / Action 비교

4-1. 반환값이 있는 경우 → Func

// 기존 사용자 정의 delegate
delegate bool NumberTest(int n);

// Func로 표현
Func<int, bool> test;

NumberTest 타입 대신 그냥 Func<int, bool>를 쓰면 된다.

4-2. 반환값이 없는 경우 → Action

// 기존 사용자 정의 delegate
delegate void MyAction(string msg);

// Action으로 표현
Action<string> myAction;

둘 다 “string 하나 받아서 void인 메서드”를 나타낸다.


5. 기존 Count 예제를 Func로 리팩터링

이전에 NumberTest를 쓰던 예제를 떠올려 보자.

delegate bool NumberTest(int n);

static int Count(int[] numbers, NumberTest test)
{
    int count = 0;
    foreach (int n in numbers)
    {
        if (test(n))
            count++;
    }
    return count;
}

이걸 Func<int, bool>로 바꾸면 이렇게 된다.

5-1. Func를 사용한 Count 예제 (전체 코드)

using System;

class Program
{
    static void Main()
    {
        int[] arr = { 3, 5, 4, 2, 6, 7, 8, 11, 13, 15, 20 };

        // Func<int, bool> : int를 받아서 bool을 반환
        int evenCount = Count(arr, n => n % 2 == 0);   // 짝수
        int oddCount  = Count(arr, n => n % 2 != 0);   // 홀수
        int gtTen     = Count(arr, n => n > 10);       // 10 초과

        Console.WriteLine("짝수 개수: " + evenCount);
        Console.WriteLine("홀수 개수: " + oddCount);
        Console.WriteLine("10보다 큰 수 개수: " + gtTen);

        Console.ReadKey();
    }

    // 델리게이트 타입 대신 Func<int, bool> 사용
    static int Count(int[] numbers, Func<int, bool> test)
    {
        int count = 0;
        foreach (int n in numbers)
        {
            if (test(n))
                count++;
        }
        return count;
    }
}

여기서 Func<int, bool> test는 예전에 썼던 NumberTest test와 같은 역할을 한다.
단지 사용자 정의 delegate 타입을 새로 만들 필요 없이, Func 한 줄로 표현한 것뿐이다.


6. Func / Action 이 진짜 많이 쓰이는 곳: LINQ

LINQ 메서드 대부분이 내부적으로 Func를 인자로 받는다.

6-1. Where 예제

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6 };

        // 짝수만 고르기
        var evens = numbers
            .Where(n => n % 2 == 0)   // 여기서 n => n % 2 == 0 은 Func<int, bool> 타입
            .ToList();

        foreach (var n in evens)
            Console.WriteLine(n);
    }
}

Where의 시그니처(개념적으로)는 대략 이런 느낌이다.

IEnumerable<T> Where(Func<T, bool> predicate)

즉, Where는 “조건을 나타내는 Func<T, bool>”를 받아서, 그 조건을 만족하는 요소만 걸러준다.

6-2. Select 예제

var squares = numbers
    .Select(n => n * n); // Func<int, int> 사용 (int 받아서 int 반환)

이런 패턴이 바로 “Func를 이용해서 동작(조건, 변환)을 바깥에서 주입하는 스타일”이다.


0개의 댓글