본문 바로가기

C#/복습

2020-05-17 델리게이트, 선언방법, 활용법, 이벤트

@델리게이트 복습

델리게이트의 기본 작동 원리를 이해하기 위해, 기본적으로 예제를 만들어 사용해 보았고,

출력하기 전에, 코드를 먼저 분석하여 주석을 달고 출력값을 예상해서 적어놓았고,

출력값은 예상한 것과 똑같이 나왔다.

 

App.Class

더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Repeat_020
{
    delegate int DelegateFunc(int a);
    class App
    {
        //매개변수 정수를 받고 정수를 반환하는 정적 메서드 Add 정의
        //정적함수도 대리자 사용가능
        static int Add(int a)   
        {
            //함수가 호출되면 문자열 Add 출력
            Console.WriteLine("Add");   
 
            //그리고 매개변수로 받은 a 를  a + a 한 값을 반환
            return a + a;
        }
 
        static int Multiplication(int a)
        {
            //함수가 호출되면 문자열 Multiplication 출력
            Console.WriteLine("Multiplication");
 
            //그리고 매개변수로 받은 a 를  a * a 한 값을 반환
            return a * a;
        }
 
        public App()
        {
            DelegateFunc delegateFunc = Add;    //대리자에 Add 함수참조
 
            //대리자의 매개변수로 10을 넘기고,
            //대리자가 곧 Add함수이기때문에 10 + 10의 결과값을 반환
            //따라서 대리자를 호출할시 대리자가 참조하고 있었던 Add 함수를 호출
            //Add함수 호출시 문자열 Add 출력
            //그리고 10 + 10의 결과값을 반환
            Console.WriteLine("delegateFunc: " + delegateFunc(10));
 
 
 
            delegateFunc = Multiplication;  //대리자에 Multiplication 함수 참조
 
            //대리자의 매개변수로 10을 넘기고,
            //대리자의 참조 메서드가 Multiplication 함수로 바뀌었기 때문에 20 * 20의 결과값을 반환
            //따라서 대리자를 호출할시 대리자가 현재 참조하고 있는 Multiplication 함수를 호출
            //Multiplication함수 호출시 문자열 Multiplication 출력
            //그리고 20 * 20의 결과값을 반환
            Console.WriteLine("delegateFunc: " + delegateFunc(20));
 
        }
    }
}
 
 
 

 

출력값

 

 

@델리게이트 선언방법

델리게이트의 선언방법크게 4가지가 있습니다.

밑의 예제는 각각의 선언방법으로, 정확한 값이 나오는지 안나오는지 시험하기 위해 작성해 보았습니다.

예상대로 다 같은 값이 나오고, 강사님이 말씀하신것 처럼, 코딩은 혼자하는 업무가 아니기 때문에, 다 똑같은 값이 나오지만, 생김새가 다 달라서 4가지는 전부는 아니더라도, 2가지 정도는 익히고 있는 것이 좋을 것같다고 생각했습니다.

 

 

App.Class

더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Repeat_020_1
{
    delegate void DelegateTest(int a, int b);   //1. 델리게이트 선언
    class App
    {
        static void Sum(int a, int b) {     //2. 델리게이트에서 참조할 함수 선언과 정의
            Console.WriteLine("a + b = " + (a + b));
        }
 
        public App()
        {
            // 1. 기본 선언
            DelegateTest dt1 = new DelegateTest(Sum);  
 
            //2: 간략한 선언
            DelegateTest dt2 = Sum;
 
            //3: 익명 함수 선언
            DelegateTest dt3 = delegate (int a, int b)
            {
                Console.WriteLine("a + b = " + (a + b));
            };
 
            //4: 람다식 선언
            DelegateTest dt4 = (a, b) =>
            {
                Console.WriteLine("a + b = " + (a + b));
            };
 
            dt1(22);  //4
            dt2(44);  //8
            dt3(66);  //12
            dt4(88);  //16
 
        }
    }
}
 
 
 

 

출력값

 

@델리게이트 함수 파라미터 활용

 

delegate void delegateFunc();

 

delegateFunc CallOkFunc;

delegateFunc CallCancelFunc;

 

public void Message(string msg, delegateFunc okFunc, delegateFunc cancelFunc)

 

이런식으로 델리게이트를 함수의 파라미터로 받아올 수 있다.


함수 하나를 만들고 여러번사용하고 재사용하기 위해 함수를 만드는건데,
그 함수의 파라미터로 델리게이트를 받아서 사용한다면, 함수의 확장성이 커지고,
그 함수가 가진 특징을 극대화 할 수 있다.

그래서 일반적으로 함수의 파라미터로 델리게이트를 받아 사용하는 경우가 굉장히 빈번하다.

 

 

App.Class

더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Repeat_020_2
{
    delegate void delegateFunc();   //1. 델리게이트 선언
    class MessageProcess    //클래스 MessageProcess 정의
    {
        delegateFunc CallOkFunc;    // 대리자 변수 CallOkFunc 선언
        delegateFunc CallCancelFunc; //대리자 변수 CallCancelFunc 선언
 
        //반환값이 없는 Message 메서드, 매개변수 문자열 msg, 델리게이트 okFunc, 델리게이트 cancelFunc
        public void Message(string msg, delegateFunc okFunc, delegateFunc cancelFunc)
        {
            //클래스 MessageProcess 대리자 delegateFunc 형식 멤버변수의 CallOkFunc에
            //매개변수로 받아온 okFunc를 할당
            CallOkFunc = okFunc;
 
            //클래스 MessageProcess 대리자 delegateFunc 형식 멤버변수의 CallCancelFunc에
            //매개변수로 받아온 cancelFunc를 할당
            CallCancelFunc = cancelFunc;
 
            //매개변수로 받아온 문자열 msg 출력
            Console.WriteLine("Message: " + msg + " (0: ok, 1: cancel)");
 
            //문자열값을 입력받아 문자열 변수 input에 할당
            string input = Console.ReadLine();
 
            //만약에 input이 문자열 "0"과 같다면
            if (input.Equals("0"))
            {
                //CallOkFunc 대리자 호출
                //하지만 현재 CallOkFunc에는 매개변수로 받아온 okFunc가 참조되어있기 때문에,
                //매개변수로 넘어온 okFunc에 해당하는 메서드가 호출이 됨.
                CallOkFunc();   
            }
            //"0"이 아니라면
            else
            {
                //CallCancelFunc 대리자 호출
                //하지만 현재 CallCancelFunc에는 매개변수로 받아온 cancelFunc가 참조되어있기 때문에,
                //매개변수로 넘어온 cancelFunc에 해당하는 메서드가 호출이 됨.
                CallCancelFunc();
            }
        }
    }
    
    class App
    {
        static void CallOk() //반환값이 없는 정적 메서드 CallOk를 선언 및 정의
        {  
            Console.WriteLine("CallOk");
        }
        static void CallCancel() //반환값이 없는 정적 메서드 CallCancel을 선언 및 정의
        {
            Console.WriteLine("CallCancel");
        }
 
        public App()
        {
            //클래스 MessageProcess 형식 msg 객체생성
            MessageProcess msg = new MessageProcess();
 
            //따라서 msg는 MessageProcess 클래스의 메서드 Message를 사용할 수 있고,
            //Message 메서드에 매개변수로 문자열 값 "Test Message"와
            //메서드 CallOk, 메서드 CallCancel을 넘겨줌.
            //따라서 위의 MessageProcess 클래스의 Message 메서드 안에 if절에 해당하는
            //대리자 CallOkFunc 와 CallCancelFunc은 각각
            //결국 CallOk , CallCancel 메서드를 호출하게 된다.
            msg.Message("Test Message", CallOk, CallCancel);
        }
 
    }
}
 
 
 

 

출력값

 

이벤트(event)

 

델리게이트와 차이점

 

-할당 연산자(=) 사용 불가

-클래스 외부 호출 불가

-클래스 멤버 필드에서 사용

 

 

이벤트에 할당연산자를 사용했을 경우

 

이벤트를 클래스 외부에서 호출할 경우

각각의 에러가 난다.

 

 

사실 델리게이트이벤트는 하는 일이 같다.


그럼 델리게이트를 사용하지 왜 굳이 이벤트를 사용하나??

일반적으로 이벤트는 클래스 외부에서 호출할 수 없다.
반드시 클래스 내부에서 불러야한다.
그렇기 때문에 안정성이 높아지고, 
이로 인하여,
델리게이트를 통해서 잘못해서 콜을 하거나,
잘못 할당하거나 이런부분들을 최소화할 수 있다라는데 큰 장점이 있다.

이벤트는 결과적으로 클래스의 캡슐화 또는 은닉성을 위해,
뭔가 좀 더 안정성을 높이기위해 사용한다.


그럼 언제 어떤 것을 사용하는 것이 좋은가?

델리게이트는 일반적으로 콜백이라고하는데 내가 어떤함수를 콜하고 그 이후에 다시 뭔가를 처리해야할 경우 많이 사용한다.


이벤트라는 건 객체, 다시말해 클래스 내부에서 인스턴스 안에서 어떤 상태변화나 이벤트를 발생하는 그런 것 들을 처리할때 사용한다.

이벤트는 클래스 내부에서만 호출할수 있기 때문에, 다른함수의 도움을 받아서 이벤트 함수를 콜할수 밖에 없음
이벤트를 만들었다는건 이벤트를 호출할수 있는 멤버 메서드를 클래스 내부에 선언해 주어야만 한다.
왜냐하면 그렇게 하지않으면 호출할 방법이 없기 때문에.

 

마지막으로 이벤트를 사용할땐 항상 델리게이트가 있어야한다, 델리게이트를 이벤트 변수로 활용해서 사용한다.

 

 

App.Class

더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Repeat_020_4
{
    public delegate void delegateEvent(string msg); // 1.델리게이트 선언
 
    class InDelegate
    {
        // 델리게이트 delegateEvent 형식 myDelegate 선언
        // 이벤트를 사용하기 위해서는 꼭 델리게이트가 필요하기 때문에 선언
        public delegateEvent myDelegate;
 
        // 델리게이트 이벤트 delegateEvent 형식 myEvent 선언
        public event delegateEvent myEvent;
 
        // InDelegate 클래스의 멤버 메서드 DoEvent 선언
        // 반환값이 없고 매개변수 정수형 값 a, b 2개를 받아옴.
        public void DoEvent(int a, int b)   
        {
            //myEvent가 null이 아닐때만 실행
            //주로 델리게이트나 이벤트는 null체크를 꼭 해줘야한다.
            //포즈 상태에도 그냥 기본적으로 해야한다고 생각하면 됨
            if (null != myEvent) 
                myEvent("DoEvent: " + (a + b)); //==ConsoleFunc("DoEvent: " + (a + b));
        }
    }
 
    class App
    {
        static public void ConsoleFunc(string msg)
        {
            Console.WriteLine("ConsoleFunc: " + msg);
 
        }
        public App()
        {
            InDelegate id = new InDelegate();   //객체화 
 
            //InDelegate의 이벤트 메서드 myEvent를 객체화 하면서 App클래스의 ConsoleFunc 메서드를 매개변수로 보냄
 
            //강사님이 설명하신것을 종합해서, 아래처럼 선언하는 코드를 보면 더 헷갈리는것 같다..
            //왜냐하면 선언시에 인스턴스가 델리게이트에 할당(참조)된다고 하셨는데
            //사실 아래코드는 인스턴스이기도하지만 ConsoleFunc 메서드 그자체이다.
            //인스턴스 == 객체? 일단 new 지정자를 사용했다는 것은 인스턴스화 했다는 것이고,
            //강사님 말대로 라면, 현재 myEvent에는 delegateEvent의 인스턴스가 참조되어 있다는 말이다.
            //여기서 객체와 인스턴스의 차이를 확립시킬 필요가 있다고 생각됐다.            
 
            id.myEvent += new delegateEvent(ConsoleFunc);
 
            //이게 보기 더 편하다.
            //현재는 객체와 인스턴스의 차이를 모르기에,코드에 보이는대로 그냥 둘은 같다고 생각해야겠다.
            //id.myEvent += ConsoleFunc; //위의 코드와 결과가 같음
 
            //id.myEvent = ConsoleFunc; //대입연산자 사용 불가..
 
            id.myDelegate = ConsoleFunc; //델리게이트라서 대입연산자 사용가능
            //id.myDelegate += ConsoleFunc; //마찬가지로 += 사용도 가능함.
 
            //위에서 InDelegate클래스의 대리자 myDelegate에 App클래스의 ConsoleFunc 메서드를 참조했고,
            //매개변수로 문자열 값 "Test"를 보냈으니, 출력값은 ConsoleFunc: Test 일 것이다.
            id.myDelegate("Test"); //클래스 외부 직접 호출 가능..
            //id.myEvent("Test"); //클래스 외부에서 직접 호출 불가...
 
 
            for (int i = 0; i < 10; i++)    //i가 10보다 작을때까지, 총 10번
            {
                //InDelegate클래스의 멤버 메서드 DoEvent에 i+1, i+2를 매개변수로 보냈다.
                //위의 InDelegate클래스 DoEvent 메서드의 안을 보면, if절 조건식이 myEvent가 null이 아닐때 실행이다.
                //하지만 App클래스에서 멤버변수 ConsoleFunc 메서드를 myEvent에 할당하면서, 인스턴스화 했기때문에,
                //null값이 아니게되고 if절 아래의 구문이 실행되어 myEvent("DoEvent: " + (a + b));가 출력된다.
                //myEvent는 ConsoleFunc를 참조하고 있기 때문에, ConsoleFunc 메서드를 출력하게 되고, myEvent의 매개변수로
                //"DoEvent: " + (a + b)를 보냈기 때문에, 결과적으로는 
                //ConsoleFunc("DoEvent: " + (a + b))와 같은 결과가 출력되어진다.
                //따라서 출력값은 ConsoleFunc: DoEvent: (a+b)가 단계별로 10번 출력될 것이다.
                id.DoEvent(i + 1, i + 2);
            }
 
        }
 
    }
}
 
 
 

 

출력값