호우동의 개발일지

Today :

article thumbnail

썸네일


델리게이트(Delegate) - 대리자

 


용도

델리게이트를 사용하면
함수 자체를 인자로 넘겨준 후, 그 함수를 호출하여 사용할 수 있다.

public class EmptyClass
{

    static void Receive(/* 함수를 인자(매개변수)로 넘겨받음*/)
    {
        // 인자로 넘겨받은 함수 실행
    }
    public static void Main(string[] args)
    {
        Receive(/* 매개변수로 넘기길 원하는 함수 */);
    }
}

Receive 함수의 매개변수로 특정 함수가 들어간 뒤,
이를 Receive 함수에서 호출할 수 있다는 것이다.

 


델리게이트 필요성

근데 이런 짓을 왜 하는 것일까?
그냥 Receive 함수 안에 실행하려는 함수를 넣거나, 코드를 넣으면 안 되나??

유니티 개발 환경에서 C#을 사용할 때 더욱 도드라지게 나타난다.

크게 2가지 이유로 나눌 수 있다.

 

1. UI와 함수와의 로직이 꼬일 수 있다.

UI 관련 컴포넌트(버튼 등)에 여러 가지 함수를 직접 넣으면,
UI와 관련된 로직과 게임 로직이 엮이면서 서로 꼬일 수 있다.

그렇기 때문에 델리게이트를 사용하여
UI와 게임 로직 관련 코드를 분리하여 관리한다.

 

2. 사용하려는 함수가 수정 불가능할 수도 있다.

이게 무슨 말이냐면, C#에 기본 내장되어 있는 기본 함수들
예를 들어 Console.WriteLine() 같은 것들은 수정이 불가능하다.

우린 그저 그것을 사용할 수밖에 없을 뿐이다.

만약 어떤 버튼이 수정 불가능한 상태로 사용만 가능하다면,
우린 매개변수로 원하는 동작(함수)을 넘겨 사용해야만 하기 때문에
델리게이트를 사용해야 한다.

 


델리게이트 사용법

using System;
namespace CSharp_Test
{

    public class EmptyClass
    {
        // 델리게이트 생성 -> 일반적인 메서드 생성 앞에 delegate만 추가
        delegate int Send();
        // delegate -> 함수를 매개변수로 넘겨주는 형식을 의미
        // int -> 반환형 int
        // Send -> 델리게이트 형식 이름
        // void -> 입력(인자)

        static void Receive(Send send)
        {

            // 인자로 넘겨받은 함수 실행
            send();
        }

        // 테스트용 함수
        // 반환 값도 맞춰줘야함
        static int Test()
        {
            Console.WriteLine("Delegate Call");
            return 0;
        }
        public static void Main(string[] args)
        {
            //방법 1
            Receive(Test);

            //방법 2
            Send instance = new Send(Test);
            Receive(instance); // 매개 변수로 넣음

            //방법 3
            Send instance2 = new Send(Test);
            instance2();
        }
    }
}

출력
출력

델리게이트를 선언하는 방법은 간단하다.
일반적인 메서드를 생성하는 것 앞에 delegate만 붙여주면 된다.

읽는 법도 메서드랑 비슷하다.
delegate는 넘겨주는 형식만 의미하고 나머지는 일반적인 함수와 같다.

방법은 총 3가지가 존재한다. 가장 간단한 방법은 1번이고
2번과 3번은 delegate형 인스턴스를 만들고
이를 Receive에 넣어주거나, 직접 호출한다.

static void Receive(Send send)
{

    send();
}

static int Test1()
{
    Console.WriteLine("Delegate Call1");
    return 0;
}
static int Test2()
{
    Console.WriteLine("Delegate Call2");
    return 0;
}
public static void Main(string[] args)
{
    Send instance = new Send(Test1);
    instance += Test2; // 함수를 여러개 넣을수도 있다. 
    Receive(instance); 
}

두 함수가 모두 실행되었다.
Test1 과 Test2 함수가 둘 다 실행됨

또한 해당 방식을 사용하면 함수를 여러 개 붙여 넣는 것도 가능하다.
이를 체인이라고 한다.

 


델리게이트의 문제점

하지만 델리게이트에는 치명적인 문제점이 있는데,

함수를 인자로 갖는 쪽을 거치지 않고도, 특정 함수를 호출할 수도 있다는 것이 문제이다.

만약 함수를 인자로 갖는 쪽에서만 실행되어야 할 함수를 다른 쪽에서도 호출할 수 있다면,
이는 은닉성에 문제가 생긴 것이기 때문에 치명적일 수 있다.

그래서 이를 보안하고자 나온 것이 이벤트(Event)이다.

 

 


이벤트(Event)

이벤트를 사용하면 은닉성을 보장받을 수 있다.

이벤트에 함수를 구독해야만 사용할 수 있는 시스템으로 돌아간다.

 


사용법

using System;
namespace CSharp_Test
{
    public class UIManager
    {
        public delegate int Send();
        public event Send SendKey; 
        // event 뒤에 자료형은 델리게이트 이름
        // 한정자도 delegate와 맞춰줘야함
        
        public void Receive()
        {

            SendKey();
        }
    }
}

다른 파일에 새로 만든 UIManager라는 스크립트 파일이다.

여기에 델리게이트 Send와 그에 맞는 event인 SendKey를 새롭게 만들었다.

여기서 delegate와 event의 한정자도 맞춰줘야 함을 주의해라

SendKey()를 함으로써 이벤트 안에 구독되어 있던 모든 함수들을 차례대로 호출한다.

using System;
namespace CSharp_Test
{

    public class EmptyClass
    {
        static int Test1()
        {
            Console.WriteLine("Delegate Call1");
            return 0;
        }
        static int Test2()
        {
            Console.WriteLine("Delegate Call2");
            return 0;
        }

        public static void Main(string[] args)
        {
            UIManager ui = new UIManager();
            ui.SendKey += Test1; // 구독
            ui.SendKey += Test2; // 구독
            ui.Receive();
        }
    }
}

다른 파일에서 자신의 함수를 SendKey에 추가함으로써 구독시킨다.
그리고 Receive를 호출하여 구독되어 있던 모든 함수를 호출한다.

Delegate와 Event의 사용방법은 위와 같았다.

사실 Event는 Delgate를 보완해 주기 위해 탄생한 것이라고 해도 과언이 아니다.