logo MSJO.kr

C# Delegate

2021-09-06
MsJ
   

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);
            }
        }
    }
}
Reference
  1. en.wikipedia.org, “Delegate (CLI)”
  2. Clint Eastwood, “Advanced C#: 08 Events”
  3. 두들낙서, “C/C++ 강좌, 94강. 함수 포인터”
  4. 마수리, “C#, 델리게이트의 모든 것(All about delegate)”
  5. High-Tech, “Lambda Expressions made easy”

Prεv(Θld)  
Content
Search     RSS Feed     BY-NC-ND