C# 기본 문법 – 대리자 편
- 1편. 대리자 -
- by tapito
C#이 객체지향 언어이기는 하지만, 함수지향형 언어가 갖는 특징을 갖기도 합니다. 이를 테면, C 언어의 함수 포인터에 해당하는 기능이 C#에도 있다는 거죠. 오히려 C 언어의 함수 포인터보다 기능이 더 강화되었습니다. 이번 시리즈에서는 C#이 갖고 있는 3가지 기능. 대리자, 무명 메서드, 람다식에 대해 알아보겠습니다.
1. 대리자(Delegate)
모든 파생된 기법의 근원
C# 초기 버전부터 있던 기능입니다. C 언어의 함수 포인터를 그대로 차용한 거나 다름없죠. 메서드의 위치를 간직하고 있으면서 그 메서드를 대신 실행해 주는 역할을 합니다. 이게 왜 필요한가? 이렇게 이해하시면 간단합니다. 해당 메서드를 직접 호출 할 수 없는 경우, 예를 들면 외부 어셈블리에 있다거나, 그 메서드가 private라던가, 아니면 호출해야 하는 메서드가 런타임 도중 동적으로 바뀌는 경우 등.
그래서 바깥에서 이 함수를 직접 호출할 수 없을 때, 대리자에게 "야! 저기 국경너머 저 함수 보이지? 여기 매개변수 줄 테니까 넘어가서 실행 좀 하고 와라!" 내지는 "야! 대리자! 나 지금 귀찮으니까 저기 있는 함수 네가 대신 좀 실행시켜라!" 뭐 이런 뜻이라고 보면 됩니다. ㅎㅎ
그럼 대리자를 어떻게 사용하느냐? 예제를 한번 봅시다.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CsTest { class Program { delegate int DelegateMethod(int a, int b); // 이런 형식의 함수를 대신 호출해 주겠다의 의미입니다. static int Average(int a, int b) { return (int)((a + b) / 2); } static void Main(string[] args) { // Average 메서드를 직접 호출하는 대신 dm이라는 인스턴스를 통해 간접 호출합니다. DelegateMethod method = Average; int a, b; Console.Write("값 a를 입력하세여 >> "); a = int.Parse(Console.ReadLine()); Console.Write("\n"); Console.Write("값 b를 입력하세여 >> "); b = int.Parse(Console.ReadLine()); Console.Write("\n"); // 여기서 Average가 간접 호출 됩니다. Console.WriteLine("(a = {0})과 (b = {1})의 연산 결과는 {2}입니다.", a, b, method(a, b)); Console.WriteLine("끝."); Console.ReadLine(); } } }
실행 결과는 다음과 같습니다.
Average메서드를 직접 실행한 것과 method를 거쳐서 실행한 것의 결과는 같습니다. 그러나 내부적으로는 이런 차이가 있죠.
delegate를 이용한 경우 |
직접 실행한 경우 |
대리자의 작동 과정을 보았으니 이제 대리자의 선언과 실제 사용 부분을 볼까요? DelegateMethod는 이렇게 선언 되었습니다.
delegate int DelegateMethod(int a, int b); |
이 선언을 인간의 언어로 풀이하자면
int형 매개변수 2개를 받아서 int형으로 반환하는 함수 꼴을 DelegateMethod라고 이름 붙이겠다
이런 의미입니다. 즉 붕어빵으로 치면 붕어빵 틀을 만든 거죠. 이제 이 틀을 이용합니다.
DelegateMethod method = Average; |
method라는 대리자를 하나 만들고 여기에 Average라는 메서드를 채워 넣습니다.
이때 Average 메서드는 int형 매개변수를 2개 받고, 반환 값의 형태가 int이니까
위에서 선언했던 DelegateMethod로 이름 붙인 형식에 꼭 들어맞습니다.
따라서 별다른 에러 없이 Average 메서드 주소가 method 대리자 속에 폭 안깁니다.
이제 실행하는 일만 남았습니다.
Console.WriteLine("(a = {0})과 (b = {1})의 연산 결과는 {2}입니다.", a, b, method(a, b)); |
굵게 표시한 부분을 보면,
특이하게도 method 인스턴스 그 자체가 마치 함수처럼 호출이 되고 있습니다.
대리자이기에 가능한 거죠.
그리고 method는 Average의 메서드 정보를 품고 있으니까 Average를 실행하게 됩니다.
여기까지가 C의 것과 다를 바 없는 함수 포인터로서의 대리자였다면
이제부터는 C#(더 나아가서 .NET에만)에는 있는 아주 특별한 대리자의 성질을 보여드리겠습니다.
우선 예제부터 볼까요?
using System; |
결과는 다음과 같습니다.
C의 함수 포인터에는 없었던 기가 막힌 기능입니다.
바로 한 대리자가 여러 메서드를 끌어 안고 있다가 연쇄적으로 몽땅 실행할 수 있다는 거죠.
단, 이 때 조건은 반환 형이 void이어야 된다는 겁니다. 생각해 보세요.
여러 개의 메서드가 같은 매개변수를 받기는 했지만, 반환 값이 제각각 다르다면
그 다른 것 중에서 무엇을 선택해야 할 지가 .NET Framework의 입장에서는 모호합니다.
그렇기 때문에 여러 개의 메서드를 대리자 안에 넣고 싶다면 반드시 반환형이 void이어야 합니다.
위의 예제를 그림으로 표현하면 다음과 같습니다.
반대로 연쇄 호출 대상에서 메서드를 뺄 수도 있습니다.
using System; |
실행 결과는 다음과 같습니다.
네. 정말 재미있는 기능이죠.
그렇다면 이런 건 어떨까요?
using System; |
Method10이라는 메서드가 새로 만들어졌습니다.
그러나 이거는 method 대리자 입장에서는 자신에게 추가되지 않았기 때문에 '듣보잡'입니다.
그런데 중간에 method -= Method10; 이런 코드가 있습니다.
원래 갖고 있지 않던 것을 빼라는 거죠. 그러면 이때는 .NET Framework가 어떤 반응을 보일까요?
네. 그냥 잘 실행 됩니다. 없는 거를 빼라는 구문을 만나면
그냥 그것에 대해서는 아무 작업도 하지 않고 스므스하게 넘어가죠.
바로 이러한 대리자를 이용해 탄생한 개념이 '이벤트'입니다.
Windows API의 경우 '이벤트'는 윈도우 프로시져로 들어오는 WM_PAINT, WM_DESTROY 등의
메시지 상수에 따라 작업을 처리했지만 완전히 객체지향화 된 C#에서는 대리자를 활용해서
'이벤트'라는 독립된 구문을 하나 만들어버렸습니다.
예를 들어서 '창 클릭'이라는 동작에 대해 처리해야 할 것이 있다면,
'창 클릭'이라는 대리자를 특별히 이벤트라는 표시를 해서 선언하고
그 대리자에게 창 클릭 시 처리한 함수들을 몰아 넣는 거죠.
그러면 실제 창이 클릭 될 때, 대리자가 품고 있던 메서드들이 연쇄적으로 모두 실행 됩니다.
이벤트를 선언하는 방법은 너무나 간단합니다. 아래와 같은 선언을 클래스 안에다 넣으면 되죠.
public event DelegateMethod WindowClick; |
그리고 여느 대리자들과 마찬가지로 +=와 -= 연산자를 이용해 메서드를 몰아 넣을 수도,
부분 뺄 수도 있습니다. 실행하는 방법도 위와 동일합니다.
여기까지가 C# 초기 버전부터 있었던 대리자 기능에 대한 설명이었습니다.
다음에는 무명 메서드에 대해 설명하겠습니다. 읽어주셔서 감사합니다.
'Programming Language > C#' 카테고리의 다른 글
C#에서 Microsoft Excel 파일 다루는 방법 (4) | 2018.02.14 |
---|---|
C# 기본 문법 – 대리자 편 2편. 무명 메서드와 람다식 (18) | 2011.02.15 |