Delegate(델리게이트, 대리자)는 C#에서 매우 중요한 개념이다. Delegate는 CLI(Common Language Infrastructure)에서 사용하는 Type-safe function pointer의 한 형태1라고 설명할 수 있다.
Method를 참조하여 호출하기에 콜백 및 이벤트 리스너2를 구현하는 데 사용한다. C++의 함수 포인터3와 같은 개념이라고 보면 된다. 단순하게 설명하면 int, string 타입처럼 함수를 변수처럼 선언하거나 함수의 파라미터로 활용4할 수 있게 해주는 것이다.
아래의 코드(소스)는 델리게이트, 함수포인터의 개념을 이해하고 이를 활용하는 예제이다. Delegate의 형태에는 Action, Func, Predicate5가 있다.
LINQ, Lambda
// Lambda와 함께 LINQ를 사용할 수 있는 이유 : Delegate (Action, Func, Predicate)
List<int> list = new() { 2, 3, 4, 5, 6, 7 };
list.Where(x => x > 5).ToList().ForEach(Console.WriteLine);
C++ 함수포인터
#include <iostream>
using namespace std;
bool compare(int a, int b) { return a == b; }
int square(int x) { return x * x; }
int myFunc(int x) { return x * (x - 15) / 2; }
int arrayMin(const int arr[], int n, int (*f)(int))
{
int min = f(arr[0]);
for (int i = 1; i < n; i++) {
if (f(arr[i]) < min) {
min = f(arr[i]);
}
}
return min;
}
int main()
{
// bool (*fp)(int, int);
// fp = &compare;
// fp = compare;
bool (*fp)(int, int) = compare;
// bool res = compare(2, 3);
// bool res = (*fp)(2, 3);
bool res = fp(2, 3);
cout << res << endl;
int arr[7] = {3, 1, 4, 1, 5, 9, 2};
cout << arrayMin(arr, 7, square) << endl;
cout << arrayMin(arr, 7, myFunc) << endl;
return 0;
}
Delegate 이해
using System;
namespace ConsoleApp1
{
internal class Program
{
private static void Main()
{
_ = new TestClass();
}
}
public class TestClass
{
private const int _NUM = 10;
private const string _STR = "가나닭";
// 위의 변수 선언처럼 아래와 같은 함수를 변수, 파라미터에 담고 싶다면???
// int function = FuncTest(): // 오류
// 델리게이트로 오류 해결 : C++에서 함수포인터 개념
public static void FuncTest()
{
Console.WriteLine("Hello World");
}
public static int Add(int a, int b)
{
return a + b;
}
private delegate void MyDelegate();
private delegate int AddDelegate(int a, int b);
private readonly AddDelegate _addDelegate = Add;
public TestClass()
{
// [1]
Console.WriteLine($"{_NUM}, {_STR}");
// [2]
MyDelegate myDelegate = FuncTest;
myDelegate();
// [3]
var res = MyFunc(_addDelegate);
Console.WriteLine(res);
}
private static int MyFunc(AddDelegate add)
{
return add(1, 2);
}
}
}
Delegate 종류
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
internal class Program
{
private static void Main()
{
List<int> list = new() { 2, 3, 4, 5, 6, 7 };
list.Where(x => x > 5).ToList().ForEach(Console.WriteLine);
// Action delegate
Action<int> myAction1 = Test1;
myAction1(10);
Action<int> myAction2 = x => Console.WriteLine(x);
myAction2(20);
// Func(tion) delegate
Func<int, int> myFunc1 = Test2;
Console.WriteLine(myFunc1(30));
Func<int, int> myFunc2 = x => x * 2;
Console.WriteLine(myFunc2(30));
Func<int, bool> myFunc3 = Test3;
Console.WriteLine(myFunc3(7));
Func<int, bool> myFunc4 = x => x == 7;
Console.WriteLine(myFunc4(7));
Func<int, int, int> myFunc5 = (a, b) => a + b;
Console.WriteLine(myFunc5(1, 2));
// Predicate delegate
Predicate<int> myPredicate1 = x => x == 7;
Console.WriteLine(myPredicate1(7));
Predicate<string> myPredicate2 = s => s.StartsWith("A");
Console.WriteLine(myPredicate2("ABC"));
// delegate 사용
int[] arr = { -10, 20, -30, 4, -5 };
int[] pos = Array.FindAll(arr, IsPositive);
foreach (int item in pos)
{
Console.WriteLine(item);
}
arr.Where(n => n >= 0).ToList().ForEach(Console.WriteLine);
}
private static void Test1(int number)
{
Console.WriteLine(number);
}
private static int Test2(int number)
{
return number;
}
private static bool Test3(int number)
{
return number != 7;
}
private static bool IsPositive(int i)
{
return i >= 0;
}
}
}
Events
using System;
namespace ConsoleApp3
{
internal class Program
{
private static void Main()
{
var tower = new ClockTower();
_ = new Person("Jone", tower);
tower.ChimeFivePm();
tower.ChimeSixAm();
}
}
public class Person
{
public Person(string name, ClockTower tower)
{
tower.Chime += (_, e) =>
{
Console.WriteLine("{0} heard the clock chime.", name);
switch (e.Time)
{
case 6:
Console.WriteLine("{0} is waking up.", name);
break;
case 17:
Console.WriteLine("{0} is going home.", name);
break;
}
};
}
}
public class ClockTowerEventArgs : EventArgs
{
public int Time { get; set; }
}
public delegate void ChimeEventHandler(object sender, ClockTowerEventArgs args);
public class ClockTower
{
public event ChimeEventHandler Chime;
public void ChimeFivePm() => Chime?.Invoke(this, new ClockTowerEventArgs { Time = 17 });
public void ChimeSixAm() => Chime?.Invoke(this, new ClockTowerEventArgs { Time = 6 });
}
}
Events 전체사용예제
using System;
namespace ConsoleExam
{
public class Car
{
public delegate void CarEndRunEventHandler(int result);
public event CarEndRunEventHandler EndRunEvent;
public void Run(int time)
{
for (int i = 0; i < time; i++)
{
Console.WriteLine("Running..." + i);
}
EndRunEvent?.Invoke(time);
}
}
internal class Program
{
private static void Main()
{
Car car = new();
car.EndRunEvent += CarEndRunEvent;
car.Run(10);
static void CarEndRunEvent(int result)
{
Console.WriteLine("Result : " + result);
}
}
}
}