Delegate와 Event의 이해(1)
이제부터 얘기해 볼 주제는 “파리~~바게트!” ..가 아니다. 바로 C#의 “Delegate And Event” Delegate와 Event란 도대체 무엇일까? 두 가지에 대해서 가장 잘 아는 방법은 그 차이점을 따져보면서 왜 쓰이게 됐는지에 대해서 살펴보는게 가장 좋다. (개발에 있어 가지는 "Why?" 는 상당히 중요하다) 그러기에 앞서 가장먼저 “파리바게트” 와 글자수마져도 같은 델리게이트(Delegate)” 란 녀석에 대해서 먼저 알아보도록 하자.
1.) Delegate(델리게이트)란 무엇일까~? - C/C++ 을 접해본 사람들은 함수포인터(Func Pointer)에 대해서 들어봤을 거라 생각한다. 접해보지 못했다는 분들을 위해 간략하게 "stdlib.h" 내에 있는 qsort함수를 살펴보면 아래와 같다.
Code Snippet
먼가 복잡해 보이는 듯 하지만 다른 건 볼 필요 없고 끝의
int(*compar)(const void *, const void *));
형태만 보자. 이 형태는 int형을 리턴하고 인자는 2개의 void형 포인터를 갖는 함수형태를 파라미터로 받는 다는 말이다. 이게 바로 함수 포인터의 정체다.(C/C++상에서)
일반적으로는 void (*pf)(int *); 위와같이 Definition 을 사용한다. (함수포인터를 콜하는 등의 예는 다루고자 하는 내용과 거리가 멀어 생략합니다.)
즉, 아주 간략히 말하자면 C#에서의 "Delegate" 는 C/C++의 함수포인터와 매우 유사한 면을 지니고 있다. C#에서 "Delegate" 는 간접 접근의 한 방법으로 사용되며 이는 "Delegate"의 사전적의미인 아래의 내용과 동일하게 사용된다.
예를 들어, 글쓴이가 저녁에 요리를 해먹기 위해 4:30분에 슈퍼에 가서 두부 한모, 파슬린, 국간장 등을 사오는 행위를 해야하는데 이를 메모지에 적어놓기만 하고 사정상 다른사람에게 부탁을 했다고 치면 부탁받음 사람은 이 일련의 작업을 메모지를 들고가서 글쓴이 대신 처리하게 되는 과정과 비슷하다. (날더운데 대신 가줄사람이 있겠냐만은....그런 착한 사람이 있다고 치자......) 여기서 이 메모장이 하는 역할이 일종의 "Delegate" 즉 대리자와 비슷하다. 그럼 이 "델리게이트" 란 녀석을 왜 쓰고 어디에 쓰는 걸까?? 위에서 잠깐 언급한 메모지를 다시 생각해보자. "예를 들어 글쓴이가 위 메모지의 내용대로 자주 음식을 해먹기 때문에 슈퍼에 자주 가야하는 상황이 생기고 또 우연히? 항상 슈퍼에 자주 갈수 없는 상황이 생겨서 누군가에게 대신 이러한 작업을 위임해야 한다고 하면 그 대상이 누가됐던간에 글쓴이는 이 메모지를 대신가줄 불쌍한 이?에게 전달만해주면 된다." 즉, "Delegate" 는 주로 코드에서 "어떠한 작업을 하고 싶은데 그 작업이 명확치 않은경우" 에 주로 쓰인다. "간단히 말하자면 위의 예에서 글쓴이가 어떤사람이건 아무나 붙잡고 심부름을 시킨다고 쳤을 때 이사람은 아직 뭘 해야 하는지 모르는 상태고 이때 글쓴이가 준 메모지가 이 심부름 하는 사람에게 어떠한 작업을 해야한다...라고 알려줄 수 있는 역할을 한다는 것이다." C#에서 자주쓰이는 대표적인 예가 "Thread" 이다. 아래 코드를 살펴보자. Code Snippet
위 코드에서 Thread가 시작됨과 동시에 Prn이라는 함수를 호출하고 "Printing........Anywhere" 라는 문자를 찍는 작업을 할 수 있는 건 ThreadStart라는 Delegate 때문이다. * Thread는 그 자체가 무엇을 실행해야 할지는 모르지만 Delegate 를 통해 해당 작업을 수행하게 된다. * ------------- ThreadStart라는 Delegate는 아래와 같이 선언되있다. Code Snippet
------------- Delegate라는 Type은 위에서 설명한 것과 같은 대리자, 역할위임의 수행을 하기 때문에 특정 동작에 대한 정보를 담고있는 Type이라고 볼 수 있다. (Type은 특정 형태를 의미하는 말이며, Instance를 의미하는 건 아니다.) Delegate의 사용법은 위의 Thread 샘플과 같은 방식으로 사용된다. 해당 Delegate Type 과 return 및 파라미터 인자들까지 그 형태가 같아야 된다. Delegate의 또다른 특징은 여러 작업을 동시에 담아둘수도 있으며 동시에 여러번의 작업을 실행 할 수도 있다는 점이다. 아래 코드를 보자. Code Snippet
위 결과를 실행하면 당연한 얘기겠지만 Prn(), Prn2() 함수의 내용이 콘솔에 출력된다. 물론 -= 연산도 가능하다. 본 이야기의 시작점이었던 "왜 쓰이고 어디에 쓰이는지" 에 대한 본론으로 들어가기 전에 조금 더 Deleagate의 내부 동작을 이해하고자 삼천포에 좀더 빠져보도록 하자 위 코드를 잠깐 ILDasm 으로 열어보도록 하자.
[ILDASM] 자세히 볼것 없이 간략히 훑어 본다면 1,2,3 번은 각각 해당 함수에 따른 Delegate 객체를 생성하는 부분이다. 위에 언급된 소스대로라면 1,3번만 나와야 맞지만 소스에 동작방식이 동일하다고 주석처리 한 부분을 확인 해 볼수 있게 실제 컴파일시에는 주석을 풀었다. 아무튼, 눈여겨 볼 곳은 System.Delegate::Combine(...,...) 과 (Combine은 Remove와 짝을 이룬다. 소스에서 "-=" 연산자로 실행 후 확인해보자) Printer::Invoke() 부분이다. 즉, 위 소스에서 Delegate Instance 에 함수를 "+=" 연산자를 통해 호출을 하게 되면 Combine메소드가 불려지면서 Code Snippet
위와 같이 컴파일러는 재 해석 하게 된다. ILDASM에서도 보이듯이rValue에 대한 Delegate Instance를 내부적으로 생성한 다음 기존의 Delegate와 결합시켜준다. 그 후 호출된 Delegate Instance 는 prn() -> prn.Invoke() -> Prn() -> Prn2() 식으로 호출과정을 거치게 된다. 컴파일하면 내부적으로 Invoke를 호출해서 하나씩 꺼내와 실행하게 되지만 직접 소스에 prn.Invoke() 라고 쳐도 실행에는 상관이 없다. Delegate에 대한 내부적인 이해를 돕기위한 삼천포는 여기 까지다.!! 왜 삼천포로 빠졌는지는 후반 2부에서 언급 될 "Event" 에 대한 설명시 비교를 위해 되새김질 해봐야 하기 때문이므로 이정도면 충분하다. (충분하지 않다면 당신은 이미.....엘리뜨?!) 다시 본론으로 들어와 그렇다면 이런 Delegate를 왜 사용하는 걸까? 그냥 아싸리 Func(); 이런식으로 호출하면 만사 땡 아닌가?? Delegate는 위에서 보여진 Combine을 통한 다수의 func에 대한 편리한? 절차적인 호출등의 이점 말고도 코드의 유연성을 도와준다는 데에 그 목적이 있다. 즉 코드간의 강한 결합도를 낮춰줄 수 있다는 말이다. 예를 들어 A 클래스 안에 항상 Data를 받으면 콘솔로 뿌려주는 Console.Write("블러") 와 같이 코드가 박혀있는 메소드가 있다고 치자. 이 A클래스를 만일 콘솔 Main함수에서 가져다 쓴다면 문제는 없겠지만 콘솔이 아닌 순수한 윈폼이라면(이는 프로젝트 설정에서 Console도 가능하게 한 경우는 제외한다.) 위 함수자체는 쓸모없게 되버릴 것이다. 만일 저 출력부를 따로 Delegate 가능하게끔 참조만을 연결해 두었다면 따로 A클래스 쓸대 해당 프로젝트에 따라서 참조로 연결해준 위임자에게 역할만 바꿔주면 될 것이다. 이 얼마나 유용성있는가? (예가 좀 억지성이 있겠지만서도..) 여기서 드러나는 또 하나의 장점은 메소드 그 자체의 캡슐화(Encapsulating) 기능이다. 호출하는 쪽에서는 그 자체 내부의 메소드 기능이 뭐든간에 그냥 대리자만 불러주면 된다. 이처럼 Delegate는 코드 구조에 따라서 참 유용하게 쓰일 수가 있다. 한 가지 빼먹은 점을 더 이야기 하자면 대리자(Delegate)는 BeginInvoke,EndInvoke를 통한 비동기 메소드 호출또한 가능하다. (MSDN을 통해 테스트 해보도록 하자- http://support.microsoft.com/kb/315582/ko) Event 와 Delegate를 한번에 이야기 하려 했으나 주저리 글이 많아지는 바람에 믿거나 말거나 + 헛소리도 많았지만 Delegate에 대한 글은 이정도로 하고 1부를 정리... 2부에서 다시 이야기를 시작하도록 하자. ** 빼먹은 내용이 있어 언급하자면 Delegate Instance 가 String과 같이 Immutable 하다는 점이다. 즉 위에 ILDASM에서도 보이지만 Combine 자체 기능도 두 개의 각기 다른 Instance를 통해 새로운 Instance를 리턴하는 것이지 기존의 Instance에 대한 변경을 가하는 점이 아니라는 것이다. ** Reference : "C# In Depth", =====
귀찮은 분을 위해 Delegate 비동기 호출을 간단히 예제를 통해 확인해 보도록 하자.
"BeginInvoke를 호출하게 될 경우 'IAsyncResult' 인터페이스를 리턴하게 되며 이는 EndInvoke시에 다시 사용된다."
[Source] Code Snippet
[Result] |
'Program > C#' 카테고리의 다른 글
서비스 응용 프로그램 만들기 (0) | 2018.01.23 |
---|---|
Delegate와 Event의 이해(2) (0) | 2017.12.10 |
StringBuilder로 문자열 처리를 빠르게 (0) | 2017.10.11 |
Invoke() 이쁘게쓰기! (0) | 2017.10.11 |
시리얼통신 (1) | 2017.10.02 |