반응형
Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
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
Tags
more
Archives
Today
Total
관리 메뉴

개발꿈나무

[C# 교과서] 21. C# 활용(15) - 대리자(delegate), 이벤트(Event) 본문

C# 기초

[C# 교과서] 21. C# 활용(15) - 대리자(delegate), 이벤트(Event)

HYOKYE0NG 2022. 1. 20. 13:27
반응형
대리자

 

대리자(delegate)는 매개변수 목록 및 반환형식이 있는 메서드 참조(포인터)를 나타내는 형식이다.

 

대리자(위임, 델리게이트)

대리자는 delegate 키워드를 사용하여 만들며, 함수 자체를 데이터 하나로 보고 의미 그대로 다른 메서드를 대신 실행하는 기능이다

  • 자동차 개체를 예로 들면, 대리 운전처럼 대리자(대리운전 기사)가 집까지 좌회전(), 우회전(), 직진(), 주차() 등 동작을 대신해서 할 수 있게 하는 개념과 비슷하다.
  • 메서드의 매개변수로 대리자 변수(개체)를 넘길 수 있다. 대리자를 사용하여 함수의 매개변수로 함수 자체를 전달할 수 있다.
  • 메서드의 매개변수로 또 다른 메서드 호출을 넘기는 기능이다.
  • 대리자는 동일한 메서드 시그니처를 갖는 메서드  참조를 담을 수 있는 그릇 역할을 한다.
  • 대리자는 람다(lambda)와 개념이 같다고 보아도 된다.
  • 대리자를 사용하면 함수를 모아 놓았다 나중에 실행하거나 실행을 취소할 수 있다.
  • 대리자는 내부적으로 MulticastDelegate 클래스에서 기능을 상속한다.
  • 대리자는 이벤트(event)를 만들어 내는 중간 단계의 키워드로 존재한다.
using System;

class DelegateDemo
{
    //[1] 함수 생성 -> 매개 변수도 없고 반환값도 없는 함수
    static void Hi() => Console.WriteLine("안녕하세요.");

    //[2] 대리자 생성 -> 매개 변수도 없고 반환값도 없는 함수를 대신 실행할 대리자
    delegate void SayDelegate();

    static void Main()
    {
        //[A] Hi 함수를 say 이름으로 대신해서 호출
        SayDelegate say = Hi;
        say();

        //[B] Hi 함수를 hi 이름으로 대신해서 호출: 또 다른 모양
        var hi = new SayDelegate(Hi);
        hi();
    }
}

대리자는 [2]처럼 delegate void 함수이름(); 형태로 구현할 수 있다.

대리자는 [A]처럼 이미 만들어 놓은 Hi 함수를 대체할 목적으로 사용할 수 있으며,

Hi 함수를 say 이름으로 대신 호출할 수 있다.

 

 

대리자를 사용하여 메서드 대신 호출하기

대리자 변수 = new 대리자(메서드 이름);
대리자 변수 += new 대리자(메서드 이름);

 

대리자로 함수 대신 호출

델리게이트로 발음하는 대리자는 함수 포인터라고도 하며 다른 함수(메서드)를 대신 호출하는 개념이다.

using System;

class DelegateNote
{
    //[1] 대리자 생성: 매개변수도 없고 반환 값이 없는 함수(메서드)를 담을 수 있는 포인터
    delegate void SayPointer();

    //[2] 샘플 함수 생성 
    static void Hello() => Console.WriteLine("Hello Delegate");

    static void Main()
    {
        //[A] 대리자의 인스턴스 생성 후 매개변수로 대신 실행할 함수명 전달 
        SayPointer sayPointer = new SayPointer(Hello);

        //[B] 대리자 인스턴스로 함수 대신 호출하는 2가지 방법
        sayPointer(); // 대리자 변수에 괄호를 붙여 메서드 호출 
        sayPointer.Invoke(); // 명시적으로 Invoke() 메서드 호출
    }
}

[A] SayPointer라는 delegate 인스턴스를 만들고 실행할 메서드 이름을 지정하는 식으로 대리자 개체 생성 가능

[B] 대리자 인스턴스 개체를 사용하여 메서드를 호출하는 두가지 방법

 

 

함수 포인터

대리자 형식은 함수 포인터(function pointer)라고도 하는데 대리자를 생성할 떄 사용되는 delegate 키워드는 이름이 없는 메서드(무명 메서드)를 만들 때도 함께 사용된다.

using System;

class FunctionPointer
{
    //[1] 함수 포인터 형식
    public delegate void Whats();

    static void Main()
    {
        //[2] 함수 포인터 정의
        Whats whats = delegate { Console.WriteLine("함수 포인터 == 대리자"); };

        //[3] 함수 포인터 호출
        whats(); 
    }
}

 

 

대리자를 사용하여 메서드 여러 개를 다중 호출하기

using System;

namespace DelegatePractice
{
    public class CarDriver
    {
        public static void GoForward() => Console.WriteLine("직진");
        public static void GoLeft() => Console.WriteLine("좌회전");
        public static void GoRight() => Console.WriteLine("우회전");
    }

    public class Insa
    {
        public void Bye() => Console.WriteLine("잘가");
    }

    // [!] 대리자 생성: 의미상으로 대리운전, class와 같은 레벨로 생성해도 됨
    public delegate void GoHome();

    public class DelegatePractice
    {
        // [!] 대리자 형식(Type) 선언 : 메서드를 묶을 별칭, 클래스 내부에 생성도 가능
        public delegate void Say();

        private static void Hello() { Console.WriteLine("Hello"); }
        private static void Hi() { Console.WriteLine("Hi"); }

        static void Main(string[] args)
        {
            // [1] 메서드 따로 따로 호출
            CarDriver.GoLeft();
            CarDriver.GoForward();
            CarDriver.GoRight();

            // [2] 대리자를 통한 메서드 등록 및 호출
            GoHome go = new GoHome(CarDriver.GoLeft);
            go += new GoHome(CarDriver.GoForward);
            go += new GoHome(CarDriver.GoRight);
            go += new GoHome(CarDriver.GoLeft); // 등록
            go -= new GoHome(CarDriver.GoLeft); // 취소
            go(); // 집에 갑시다... 한번 호출

            Console.WriteLine();

            // [3] 대리자를 통해서 한번에 2개의 메서드 호출...
            Say say;                // [a] 대리자 형 변수 선언
            say = new Say(Hi);      // [b] Hi 메서드 지정
            say += new Say(Hello);  // [c] Hello 메서드 지정
            say();                  // [d] 대리자로 두 개의 메서드 호출
            
            // [4] 대리자를 통해서 호출
            Insa insa = new Insa();
            Say say2 = new Say(insa.Bye);
            say2 += new Say(insa.Bye);
            say2();
        }
    }
}

 

 

무명 메소드

// 무명 메서드(Anonymous Method): 대리자를 축약해서 표현하는 방법
using System;

namespace AnonymousMethod
{
    public class Print
    {
        public static void Show(string msg) => Console.WriteLine(msg);
    }

    public class AnonymousMethod
    {
        //[!] 대리자 선언
        public delegate void PrintDelegate(string msg);
        public delegate void SumDelegate(int a, int b);
        static void Main()
        {
            //[1] 메서드 직접 호출
            Print.Show("안녕하세요.");

            //[2] 대리자에 메서드 등록 후 호출
            PrintDelegate pd = new PrintDelegate(Print.Show);
            pd("반갑습니다.");

            //[3] 무명(익명) 메서드로 호출: delegate 키워드로 무명 메서드 생성 
            PrintDelegate am = delegate (string msg)
            {
                Console.WriteLine(msg);
            };
            am("또 만나요.");

            //[4] 무명메서드 생성 및 호출
            SumDelegate sd = delegate (int a, int b) { Console.WriteLine(a + b); };
            sd(3, 5); // 8
        }
    }
}

 

대리자 개체에 람다 식 담기

using System;

class LambdaExpression
{
    //[1] 대리자 선언
    delegate void Lambda();
    static void Main()
    {
        //[2] 대리자 개체에 람다식 정의: goes to 연산자
        Lambda hi = () => Console.WriteLine("안녕하세요.");
        //[3] 대리자 개체 호출
        hi();
    }
}
using System;

class LambdaExpressionArgs
{
    //[1] 매개 변수도 있고 반환값도 있는 대리자 선언
    delegate int Lambda(int i);
    static void Main()
    {
        //[2] 람다 식으로 대리자 개체 생성
        Lambda square = x => x * x; 
        Console.WriteLine(square(3)); // 9
        Console.WriteLine(square(4)); // 16
    }
}

 

 

메서드의 매개변수에 대리자 형식 사용하기

using System;

class DelegateParameter
{
    delegate void Runner();

    static void Main()
    {
        RunnerCall(new Runner(Go));     // "직진"
        RunnerCall(new Runner(Back));   // "후진"
    }

    static void RunnerCall(Runner runner) => runner(); // 넘어온 메서드(함수) 실행        
    static void Go() => Console.WriteLine("직진");
    static void Back() => Console.WriteLine("후진");
}

 

 

Action, Func, Predicate 대리자

닷넷 API에 내장된 유용한 제네릭 대리자에넌 Action, Func, Predicate 가 있다.

  • Action 대리자: 반환값이 없는 메서드를 대신 호출한다.
  • Func 대리자: 매개변수와 반환값이 있는 메서드를 대신 호출한다.
  • Predicate 대리자: T 매개변수에 대한 bool 값을 반환하는 메서드를 대신 호출한다.

 

Action<T> 대리자 사용하기

Action 제네릭 대리자를 사용하면 Console.WriteLine 같은 메서드를 대신 호출할 수 있다.

Action<string> printf = Console.WriteLine;
printf("메서드 대신 호출");

 

Func<T> 대리자 사용하기

Func<매개변수 형식, 반환값 형식>으로 특정 메서드 또는 익명 메서드를 대신 호출할 수 있다.

Func 제네릭 대리자는 람다 식을 포함한 무명 메서드 또는 일반 메서드를 대신 호출할 수 있다.

using System;

class FuncDemo
{
    static void Main()
    {
        // [1] int를 입력 받아 0이면 true을 반환
        Func<int, bool> zero = number => number == 0;
        Console.WriteLine(zero(1234 - 1234)); // True

        // [2] int를 입력 받아 1을 더한 값을 반환
        Func<int, int> one = n => n + 1;
        Console.WriteLine(one(1)); // 2

        // [3] int 2개를 입력 받아 더한 값을 반환
        Func<int, int, int> two = (x, y) => x + y;
        Console.WriteLine(two(3, 5)); // 8
    }
}

 

Fucn 대리자로 메서드 또는 람다 식 대신 호출하기

using System;

class FuncDelegate
{
    static void Main()
    {
        // [1] Add 함수 직접 호출
        Console.WriteLine(Add(3, 5));

        // [2] Func 대리자로 Add 함수 대신 호출: 반환값이 있는 메서드를 대신 호출
        Func<int, int, string> AddDelegate = Add; // Add 메서드를 대신 호출
        Console.WriteLine(AddDelegate(3, 5));

        // [3] 람다식(Lambda): 메서드 -> 무명메서드 -> 람다식으로 줄여서 표현
        Func<int, int, string> AddLambda = (a, b) => (a + b).ToString(); 
        Console.WriteLine(AddLambda(3, 5));
    }

    static string Add(int a, int b) => (a + b).ToString();
}

Func 대리자를 사용하면 동일한 매개변수와 반환값이 있는 메서드를 대신해서 호출할 수 있다.

 

 

Predicate 대리자 사용하기

Predicate<T> 대리자는 T를 매개변수로 받아 어떤 로직을 수행한 후 그 결과를 bool 형식으로 반환하는 메서드를 대신 호출한다.

using System;

public class PredicateDemo
{
    static void Main()
    {
        Predicate<string> isNullOrEmpty = String.IsNullOrEmpty;

        Console.WriteLine(isNullOrEmpty("Not Null"));

        Predicate<Type> isPrimitive = t => t.IsPrimitive;

        Console.WriteLine(isPrimitive(typeof(int)));
    }
}

 

 

 

이벤트

 

이벤트는 특정 상황이 발생할 때 개체 또는 클래스에서 알림을 제공할 수 있도록 하는 멤버로, 버튼 클리고가 마우스 오버 같은 이벤트 기반 프로그래밍에 사용하는 개념이다.

 

이벤트

 

  • 이벤트는 개체의 메서드 실행 결과(사고)를 나타낸다.
  • 자동차 개체를 예로 들면, 과속이라는 동작(메서드)의 수행 결과는 교통사고라는 이벤트(사고)가 발생한다는 의미로 해석할 수 있다.
  • 웹 응용 프로그래밍 및 데스크톱 응용 프로그램은 이벤트 기반 프로그래밍이라고 할 정도로 많은 이벤트를 사용한다.
  • 마우스 클릭 이벤트, 마우스 오버 이벤트, 마우스 아웃 이벤트 등을 표현할 때는 이벤트 기능으로 정의한다.

 

프로그래밍에서 이벤트(event)와 이벤트 처리기(event handler)는 다음과 같이 표현된다.

  • 이벤트: 클릭과 마우스 오버 같은 동작(트리거)
  • 이벤트 처리기: 특정 이벤트를 담당하려고 만든 메서드

 

이벤트와 대리자를 사용하여 메서드 등록 및 호출하기

대리자는 이벤트를 위한 중간 단계고, 이벤트는 메서드 여러 개, 특히 이벤트 처리 전용 메서드라는 이벤트 처리기(핸들러) 메서드를 등록한 후 실행시키는 역할을 한다.

using System;

public class ButtonClass
{
    //[1] 이벤트 생성을 위한 대리자 하나 생성
    public delegate void EventHandler(); // 여러 개 메서드 등록 후 호출 가능

    //[2] 이벤트 선언: Click 이벤트 
    public event EventHandler Click;

    //[3] 이벤트 발생 메서드: OnClick 이벤트 처리기(핸들러) 생성
    public void OnClick()
    {
        if (Click != null) // 이벤트에 등록된 값이 있는지 확인(생략 가능)
        {
            Click(); // 대리자 형식의 이벤트 수행
        }
    }
}

class EventDemo
{
    static void Main()
    {
        //[A] Button 클래스의 인스턴스 생성
        ButtonClass btn = new ButtonClass();

        //[B] btn 개체의 Click 이벤트에 실행할 메서드들 등록
        btn.Click += Hi1; // btn.Click += new ButtonClass.EventHandler(Hi1);
        btn.Click += Hi2; // btn.Click += new ButtonClass.EventHandler(Hi2);

        //[C] 이벤트 처리기(발생 메서드)를 통한 이벤트 발생: 다중 메서드 호출
        btn.OnClick();
    }
    static void Hi1() => Console.WriteLine("C#");
    static void Hi2() => Console.WriteLine(".NET");
}

 

 

 

 

 

<Reference>

 

 

반응형
Comments