포스트

[delegate] 03. 익명 델리게이트와 Func, Action

익명 델리게이트(Anonymous Delegate)

델리게이트에 메서드 참조를 저장하기 위해 함수의 식별자(이름)을 제공하지만, 가끔씩은 특정 함수 내에서만 사용되어 함수를 따로 만들어 둘 필요가 없을 때도 있습니다. (이름이 필요 없는 일회용 함수를 사용하고 싶을 때)
이럴 때, 이름이 명시되지 않은 델리게이트인 익명 델리게이트를 사용할 수 있습니다.

익명 델리게이트는 델리게이트 키워드를 사용하여 선언되며, 이후에 실행할 메서드의 본문이 바로 정의됩니다.

이렇게 함으로써 별도의 메서드를 정의할 필요 없이 코드를 간결하게 만들 수 있습니다.

1
delegate(매개변수){ // 실행할 코드 };

예를 들어, 어떤 숫자 배열에서 짝수나 홀수의 개수를 세는 동작을 만든다고 해봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public delegate bool Predicate<T>(T x);

static class Utilities
{
    public static int Count<T>(IEnumerable<T> items, Predicate<T> predMethod)
    {
        int count = 0;
        foreach (var item in items)
        {
            if (predMethod(item))
            {
                count++;
            }
        }
        return count;
    }
}

internal class Program
{
	static void Main(string[] args)
	{
		var numbers = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9};
		
		int oddNumbers = Utilities.Count(numbers, delegate(int n) { return n % 2 != 0; });
		Console.WriteLine(oddNumbers); // 5
		
		int evenNumbers = Utilities.Count(numbers, delegate(int n) { return n % 2 == 0; });
		Console.WriteLine(evenNumbers); // 4
	}
}

위 예제처럼, 어떤 수를 세기 위한 유틸리티로 범용성을 높이기 위해 타입 T에 맞는 컬렉션과 사용자 정의 델리게이트를 받아옵니다.
이때, 각각 홀수와 짝수를 판단하기 위한 메서드는 이 Utilities.Count를 호출하는 위치에서 각각 알맞는 함수를 넘겨 받으면 되기 때문에, 굳이 이름을 갖는 메소드를 따로 정의하지 않고 이름 없이 인라인 메서드로 넘겨줄 수 있습니다.

이렇게 이름 없이 본문이 바로 정의된 메서드 델리게이트를 익명 델리게이트라고 합니다.

Func, Action 델리게이트

하지만, 위에서처럼 익명 델리게이트를 사용하는데 있어서 굳이 델리게이트 선언부(public delegate bool Predicate<T>(T x);)가 필요할까?하는 생각이 들게 됩니다.

이럴때 쓸 수 있는게 Func<>Action<>입니다.

C#의 FuncAction 델리게이트는 익명 메서드(익명 델리게이트나 람다식 등…)를 정의하고 사용하는 데 매우 유용한 기능입니다.

기존 델리게이트는 명시적으로 정의해야 하며, 이를 매개변수로 사용할 때도 명시적으로 타입을 지정해야 하지만,
Func<>Action<>기존의 델리게이트보다 간결하고 델리게이트 정의 없이 간편하게 사용할 수 있는 제네릭 델리게이트입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static class Utilities
{
    public static int Count<T>(IEnumerable<T> items, Func<T, bool> predMethod)
    {
        int count = 0;
        foreach (var item in items)
        {
            if (predMethod(item))
            {
                count++;
            }
        }
        return count;
    }
}
// ...
var numbers = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9};

int oddNumbers = Utilities.Count(numbers, delegate(int n) { return n % 2 != 0; });
Console.WriteLine(oddNumbers); // 5

int evenNumbers = Utilities.Count(numbers, delegate(int n) { return n % 2 == 0; });
Console.WriteLine(evenNumbers); // 4

위의 예제는 명시적인 델리게이트 선언 없이, Func<T, bool>로 바로 익명 델리게이트를 받아오는 모습을 볼 수 있습니다.

Func 델리게이트

Func 델리게이트반환 값을 가지는 메서드를 나타내는 데 사용됩니다.

최대 16개의 매개변수를 받을 수 있으며, 마지막 타입 매개변수가 반환 값의 타입을 나타냅니다.

1
2
3
4
5
6
7
8
internal class Program
{
	static void Main(string[] args)
	{
		Func<int, int, int> add = delegate(int x, int y) { return x + y; };
		Console.WriteLine( add(3, 4) ); // 7
	}
}

위의 예에서 Func<int, int, int>는 두 개의 int 매개변수를 받아 int를 반환하는 델리게이트를 정의합니다.
이때, 델리게이트를 명시적으로 선언 할 필요가 없어, 함수 내부에서 직접 만들어 사용할 수도 있습니다.

Action 델리게이트

Action 델리게이트반환 값이 없는 메서드를 나타내는 데 사용됩니다.

Action도 최대 16개의 매개변수를 받을 수 있습니다.

1
2
3
4
5
6
7
8
internal class Program
{
	static void Main(string[] args)
	{
		Action<int> print = delegate(int x) { Console.WriteLine(x); };
		print(10); // 10
	}
}

위의 예에서 Action<int>int 타입의 매개변수를 받고 바로 출력하는 델리게이트를 정의합니다.
이 역시, 델리게이트를 명시적으로 선언 할 필요가 없어, 함수 내부에서 직접 만들어 사용할 수도 있습니다.

Func와 Action을 델리게이트로 사용

그렇다면, Func<>Action<>은 익명 델리게이트를 받는데에만 사용하는가? 하면 그렇지는 않습니다.

위에서 간단하게 만든 예제에서 볼 수 있듯이 익명 델리게이트를 바로 바로 만들어서 사용할 수도 있지만, 기존의 델리게이트처럼 혹은 이벤트처럼 필드에 저장하고 호출하는 방식으로도 쓸 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class TestClass
{
    private Action ActionDelegate;
    private Func<int, int, int> AddDelegate;
    public void AddAction(Action action)
    {
        ActionDelegate += action;
    }
    public void OnAction()
    {
        ActionDelegate?.Invoke();
    }
}
internal class Program
{
	private static void ActionTest()
	{
	    Console.WriteLine("ActionTest");
	}
	static void Main(string[] args)
	{
		TestClass testClass = new TestClass();
		testClass.AddAction(ActionTest);
		testClass.OnAction(); // ActionTest
	}
}

위 예제에서 보듯이 event처럼 델리게이트 없이 필드에 델리게이트를 저장하는 필드를 만들 수 있고, += 연산자로 명명된 메서드(이름이 있는 메서드)를 연결한 뒤 호출할 수도 있습니다.


이번 글에서는 익명 델리게이트에 대해 설명하고, Func와 Action을 다루는데, 익명 델리게이트를 사용했습니다.
그러나 현대의 C# 개발 환경에서는 이러한 익명 델리게이트보다 더 간결하고 효율적인 람다 표현식을 더 자주 사용합니다.
람다에 대해서는 다음에 다루도록 하겠습니다.


참고

C#의 람다(Lambda)와 캡처(Capture), 클로저(closure)

[delegate] 01.델리게이트의 정의와 정체, 체이닝, 사용 목적

[delegate] 02. event와 EventArgs (EventHandler)

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.