NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 28. Delegate (델리게이트, 대리자, 위임자)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 정말 오랜만에 C# 관련 글을 적게 되는군요-_-; 요즘 박근혜와 최순실 사건이 너무 드라마틱하게(?) 전개 되다보니 페이스북(Facebook)과 뉴스를 보느라 집에서 작업할만한 시간을 못내고 있었습니다. 아무튼 청와대에서 비아그라 구매 기사가 나온 이후로 좀 뜸해져서, 머리속에 있는 내용도 정리할겸 Delegate에 대해서 알아보겠습니다.

    Yg05WcU.jpg

     

     

    델리게이트는 C 및 C++의 함수 포인터처럼 메소드를 안전하게 캡슐화하는 형식입니다. 함수 포인터와는 달리 델리게이트는 객체 지향적이고 형식이 안전하며 보안이 유지됩니다. 델리게이트의 형식은 델리게이트의 이름으로 정의됩니다. - 출처: 마이크로소프트

     

    위 내용이 너무 추상적인데다가 처음 대리자를 만나게되면 개념이 모호할 수 있습니다-_-; 이를 좀 더 쉽게 풀어서 이야기해보면 어떤 메소드가 처리해야 할 것을 델리게이트로 정의한 변수가 처리를 위임할 수 있도록 하는 것입니다. 따라서, 델리게이트를 호출하게 되면 이를 위임 받은 대리자(메소드)들이 실행되게 됩니다.

     

    간단한 예제를 통해 델리게이트의 사용 방법을 먼저 익힌 후 좀 더 자세하게 알아보도록 하겠습니다. 사용법을 알게되면 이걸 왜 사용해야 하는지에 대해 궁금증이 생길겁니다.

    먼저, 윈폼 프로젝트를 하나 생성한 후 Form1.cs에 아래와 같이 델리게이트를 선언 하세요.

    Form1.cs

    using System.Windows.Forms;
     
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public delegate void Popup(string message);
     
    

     

     

    Popup Delegate에 위임할 메소드를 추가하세요. 메소드는 델리게이트와 Syntax가 일치해야 합니다. 아래처럼 말이죠.

    Form1.cs

    public void MessagePopup(string message)
    {
        MessageBox.Show(this, message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

     

     

    참고로, 메소드 오버로딩의 경우에는 반환 값의 형식은 달라도 문제되지 않는데 반해 델리게이트는 반환 값의 형식까지 위임할 메소드와 일치해야 합니다. 델리게이트는 일반적으로 위임할 메소드의 이름을 인자로 넘기거나 무명 메소드를 사용하여 생성합니다. 타입이 명확하다면 메소드를 바로 할당해도 됩니다. var와 같이 컴파일 타임에 형식을 유추하는 익명 변수를 사용할 경우 아래처럼 초기화 할 수 있습니다.

    Form1.cs

    using System.Windows.Forms;
     
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public delegate void Popup(string message);
     
            public void MessagePopup(string message)
            {
                MessageBox.Show(this, message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
     
            public Form1()
            {
                InitializeComponent();
     
                var popup = new Popup(MessagePopup);
                popup("메소드를 위임할 수 있습니다.");
            }
        }
    }

     

     

    형식을 유추할 필요가 없는 경우 아래처럼 간단하게 메소드를 할당할 수 있습니다.

    Form1.cs

    public Form1()
    {
        InitializeComponent();
     
        Popup popup = MessagePopup;
        popup("메소드를 위임할 수 있습니다.");
    }

     

     

    이 응용 프로그램을 실행하면 "메소드를 위임할 수 있습니다."를 보여주는 팝업이 표시됩니다. 그리고, 기본 폼이 화면에 표시되죠. 델리게이트는 봉인(sealed)되어 있으므로 파생될 수 없습니다. 인스턴스화된 델리게이트는 개체이므로 매개 변수로 전달하거나 속성에 할당할 수 있습니다. 따라서 메소드가 대리자를 매개 변수로 허용하고 나중에 대리자를 호출할 수 있습니다.

    아래 코드는 익명 메소드를 사용하는 방법을 보여줍니다.

    Form1.cs

    public Form1()
    {
        InitializeComponent();
     
        Popup popup = delegate(string message)
        {
            MessageBox.Show(this, message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        };
     
        popup("익명 메소드를 사용합니다.");
    }

     

     

    위와같이 일회성이거나 또는 이 시컨스에만 사용될 때 별도로 메소드를 추가하지 않고 익명 메소드를 사용합니다. 아무튼, 익명 메소드에 대한 내용은 이 글과 관련이 없기 때문에 일단 이정도만 알아보고 넘어가겠습니다. 그렇다면, 왜 대리자(Delegate)를 사용할까요? 위 코드만 봐서는 별도의 클래스에 메소드 하나 추가한 후 호출해도 동일하게 동작하기 때문에 "왜 델리게이트를 사용하지?"에 대한 물음에 답을 해줄수는 없습니다.

    fWn6dyc.jpg

     

     

    대리자는 말 그대로 어떤 일을 대신 해주는 것을 말합니다. 예를들면 대리운전 회사가 있고, 이 회사에는 대리 운전 기사들이 여러명 있을겁니다. 회사(프레임워크 또는 모듈)는 언제 고객에게 연락이 올지 모르고 어디로 갈지에 대해 알 수 없습니다. 그렇기 때문에 미리 강동, 강서, 강남, 강북 대리 기사들을 고용(델리게이트 정의)한 것입니다. 이 때 각각의 기사(클라이언트 개발자)들이 어떤 방식으로 목적지에 도착하는지는 회사 입장에서 관심의 대상이 아닙니다.

     

    좀 더 다양한 예들이 있지만, 우선 위 이야기를 단순화 해보면 술을 마신 고객이 잠실로 가자고 연력이 오면 대리운전 회사는 이 의뢰를 대리 기사들에게 위임합니다. 그리고, 대리 기사는 자신이 처리해야한다고 판단이 되면 해당 고객을 잠실까지 대려다 주게 됩니다. 이 내용을 코드로 옴겨보면 좀 더 쉽게 이해할 수 있을겁니다.

    먼저 화면을 구성합니다.

    3APx2pz.png

     

     

    목적지를 입력할 수 있는 텍스트박스는 txtLocation으로, 버튼은 callButton으로 이름을 변경한 후 코드 보기로 전환하세요.

    Form1.cs

    using System;
    using System.Windows.Forms;
     
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public delegate void DelegateCall(string message);
     
            public void Call(string message)
            {
                MessageBox.Show(this, message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
     
            public Form1()
            {
                InitializeComponent();
     
                this.callButton.Click += CallButton_Click;
            }
     
            private void CallButton_Click(object sender, EventArgs e)
            {
                // 대기중인 기사들을 호출한다.
                var 강북_대리기사 = new DelegateCall(Call);
                var 강남_대리기사 = new DelegateCall(Call);
                var 강서_대리기사 = new DelegateCall(Call);
                var 강동_대리기사 = new DelegateCall(Call);
     
                var 전지역 = 강북_대리기사 + 강남_대리기사 + 강서_대리기사 + 강동_대리기사;
                 
                switch (this.txtArea.Text)
                {
                    case "강북": 강북_대리기사("강북(으)로 모시겠습니다."); break;
                    case "강남": 강남_대리기사("강남(으)로 모시겠습니다."); break;
                    case "강서": 강서_대리기사("강서(으)로 모시겠습니다."); break;
                    case "강동": 강동_대리기사("강동(으)로 모시겠습니다."); break;
                    default: 전지역($"{this.txtArea.Text}(으)로 모시겠습니다."); break;
                }
            }
        }
    }
    

     

     

    이벤트에 대한 내용은 다음에 알아보기로 하겠습니다. 버튼을 직접 만들고, 이벤트를 외부로 노출해야 하는데 이 부분까지 여기에서 다루면 내용이 너무 길어지기 때문입니다. 사실, 델리게이트와 이벤트는 사용하는 용도가 비슷해서 같이 언급되기도 합니다. 이벤트 핸들러도 델리게이트 형식이고, 외부에 이벤트를 노출할 때 내부 델리게이트를 구현한 EventHandler를 사용합니다.

     

    위 코드의 내용은 단순합니다. 사용자가 특정 지역을 입력하고 호출하면, callButton의 델리게이트에 위임된 callButton_Click메소드가 실행됩니다. 회사는 어느 시점에 고객에게 연락이 올지 알 수 없기 때문에 callButton(프레임워크 또는 모듈)에서 제공하는 이벤트 핸들러를 구현하고 있습니다. 그리고, 위임 받은 메소드에서 어떤 대리 기사에게 Order를 내릴지 판단한 후 다시 DelegateCall을 통해 위임합니다. 물론, 개별적으로 클래스와 메소드를 구현하지는 않았기에 switch~case를 통해 그렇게 보이도록 처리한 것 뿐입니다.

            private void CallButton_Click(object sender, EventArgs e)
            {
                // 대기중인 기사들을 호출한다.
                var 강북_대리기사 = new DelegateCall(Call);
                var 강남_대리기사 = new DelegateCall(Call);
                var 강서_대리기사 = new DelegateCall(Call);
                var 강동_대리기사 = new DelegateCall(Call);
    
                var 전지역 = 강북_대리기사 + 강남_대리기사 + 강서_대리기사 + 강동_대리기사;
    
                switch (this.txtArea.Text)
                {
                    case "강북": 강북_대리기사("강북(으)로 모시겠습니다."); break;
                    case "강남": 강남_대리기사("강남(으)로 모시겠습니다."); break;
                    case "강서": 강서_대리기사("강서(으)로 모시겠습니다."); break;
                    case "강동": 강동_대리기사("강동(으)로 모시겠습니다."); break;
                    default: 전지역($"{this.txtArea.Text}(으)로 모시겠습니다."); break;
                }
            }

     

     

    꼭 어떤 이벤트에 대한 것뿐만 아니라, UI를 가지는 멀티 스레드에서도 델리게이트는 자주 사용됩니다. UI는 자신이 만들어진 작업자 스레드에서만 접근을 허용합니다. 하지만, 멀티 스레드 환경에서 특정 UI를 업데이트하기 위해 항상 작업자 스레드로 이동할 수는 없습니다. 이 때 크로스 스레드 에러가 발생하게 됩니다. 그래서, 작업자 스레드로 이동하기 위해 호출하게 되는데 언제 호출될지 알 수 없는 문제가 발생됩니다. 물론, 거의 바로 호출되긴 합니다. 아무튼 이 때에도 델리게이트를 이용하고, 스레드를 실행할 때도 델리게이트가 사용됩니다.

    gxBbvvz.png

     

     

    델리게이트에 대한 내용이 워낙 방대해서 몇번에 걸쳐서 진행될듯 한데요. 우선, 다음 글에서 이벤트에 대해 알아본 후 멀티 스레딩과 Func, Action에 대해 알아보면 델리게이트와 관련된 내용을 마칠 수 있을거 같습니다. 결국 내부 구현은 델리게이트가 사용되고 있기 때문에 이어서 진행되는 내용도 모두 보면 델리게이트를 이해하는데 조금은 도움이 될거 같습니다. 하지만, 일반적인 개발에 있어서는 C#에서 제공하는 랩핑된 시스템 델리게이트를 주로 사용하다보니 크게 와닿거나 하지는 않는게 사실입니다. 대부분의 개발자들이 알게모르게 사용하고 있고, 어떻게 사용해야 하는지도 잘 알고 있습니다.

    qJH4Wsq.png

     

     

    이렇게 해서 Delegate의 기본적인 사용 방법만 알아봤습니다. 다음에 알아보게 될 이벤트에서는 아직 설명하지 않은 몇가지 추가 기능에 대해 알아보고, 간단한 컨트롤을 하나 만들어서 이벤트를 연결해보도록 하겠습니다.

     

    업무 자동화 RPA 매크로 제작 및 견젹 문의 ]

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.