작성날짜 : 2011-04-23 |
출처 : http://cafe.daum.net/smbitpro?t__nil_cafemy=item
참고 사이트 : http://msdn.microsoft.com/ko-kr/library/ms173171.aspx
Delegate (대리자)
대리자는 메서드 시그니처를 정의하는 형식으로 반환 형식도 호환되어야 합니다.
그리고, 대리자는 암시적 클래스로 컴파일러를 통해 Delegate클래스에서 파생된 MulticastDelegate에서 파생됩니다.
C나 C++의 함수 포인터와 비슷한 역할을 하지만 함수 포인터와 달리 대리자 개체는 관리화 대상인 안전한 형식입니다.
참고로, Delegate 클래스와 MulticastDelegate는 시스템과 컴파일러만 이를 기반으로 파생시킬 수 있습니다.
그리고, 대리자는 암시적 봉인된 클래스로 기본 클래스가 될 수 없습니다.
대리자는 시그니처가 호환되는 모든 메서드를 연결할 수가 있으며 대리자를 통해 연결된 메서드를 호출이 가능합니다. 또한, 여러 메서드를 하나의 대리자 형식 변수에 +연산자를 통해 결합 및 – 연산자를 통해 연결 제거가 가능하며 호출이 가능합니다. 그리고, MulticastDelegate또한 시스템과 컴파일러만 파생시킬 수 있습니다.
대리자를 이용하여 이벤트를 정의할 수 있으며 해당 이벤트에 연결된 메서드는 이벤트 처리기라 합니다. 여러분들은 WinForm과 같은 윈도우 프로그래밍이나 웹 프로그래밍을 하게 되면 자연스럽게 이벤트를 만나게 될 것입니다. 당연히 대리자를 통해 콜백을 정의할 수 있습니다.
컴파일러에 의해 생성되는 대리자 클래스에 노출된 주요 멤버들을 알아봅시다.
먼저 생성자가 노출이 되는데 입력 인자는 object와 메서드 주소에 해당하는 int가 오는데 프로그래밍을 할 때에는 정의한 시그니처와 일치하는 메서드를 입력인자로 넣으면 컴파일러가 적절한 코드로 변경을 해 줍니다.
그리고, 연결된 메서드를 동기식으로 호출해 주는 Invoke메서드가 있는데 시그니처는 대리자에 정의한 것과 일치하게 됩니다. 프로그래밍을 할 때 Invoke메서드를 직접 호출할 수도 있지만 대리자 형식 변수를 함수처럼 호출을 하면 내부적으로 Invoke메서드를 호출해 줍니다.
또한, BeginInvoke메서드를 통해 연결된 메서드를 비동기식으로 호출을 할 수도 있습니다. 리턴 형식은 비동기 작업을 종료하면서 필요한 IAsyncResult이며 입력 매개변수는 대리자를 정의할 때의 입력 매개변수들이 앞에 오며 그 뒤에 비동기 작업이 종료되면 자동적으로 호출되는 AsyncCallback과 AsyncCallback으로 전달할 object가 있습니다. 그리고, 비동기 작업을 종료화 시키는 EndInvoke가 있습니다.
개발도구를 통해 대리자에 대해 좀 더 살펴보기로 합시다. 프로젝트 명은 AboutDelegate라 할께요.
대라자에 대한 설명을 하기 위한 Education이름으로 코드 파일을 하나 추가할께요.
먼저 학생 클래스를 정의해 봅시다.
번호와 이름, iq를 위한 필드를 선언하고 iq의 최소값과 최대값을 상수로 선언하세요.
동일하게 hp를 위한 필드와 hp의 최소값과 최대값을 상수로 선언하십시요.
생성자는 번호와 이름을 입력 인자로 받아 대입하고 iq와 hp는 최소값으로 대입하였습니다.
그리고 ToString메서드를 재정의하여 번호와 이름 iq, hp에 관한 문자열를 반환하도록 하세요.
class Stu { const int min_iq = 60; const int max_iq = 200; const int min_hp = 0; const int max_hp = 100; int num; string name; int iq; int hp;
public Stu(int _num, string _name) { num = _num; name = _name; iq = min_iq; hp = min_hp; } public override string ToString() { return string.Format("번호 : {0} 이름 : {1} 아이큐 : {2} 체력 : {3}", num, name, iq, hp); } } |
그리고 iq필드를 위한 속성을 하나 추가하고요.
get은 iq를 그대로 반환하고 set에서는 iq가 최소값과 최대값 범위내에서 설정되게 해야겠죠.
public int Iq { get { return iq; } set { if (value < min_iq) { iq = min_iq; } else if (value > max_iq) { iq = max_iq; } else { iq = value; } } } |
그리고, 같은 원리로 Hp속성을 추가하세요.
get에서는 hp를 그대로 반환하고 set에서는 min_hp와 max_hp사이의 값을 유지하게 해야겠죠.
public int Hp { get { return hp; } set { if (value < min_hp) { hp = min_hp } else if(value > max_hp) { hp = max_hp; } else { hp = value; } } } |
그리고, 대리자에서 위임할 메서드로 리턴 형식이 void이고 입력 인자가 int tcnt인 Study메서드를 추가합시다.
public void Study(int tcnt) { Console.WriteLine("학습 전"); Console.WriteLine(this.ToString()); iq += tcnt; Console.WriteLine(tcnt.ToString() + "시간 학습 후"); Console.WriteLine(this.ToString()); } |
마찬가지로 대리자에서 위임할 메서드인 Play를 추가합시다.
public void Play(int tcnt) { Console.WriteLine("운동 전"); Console.WriteLine(this.ToString()); hp += tcnt; Console.WriteLine(tcnt.ToString() + "시간 운동 후"); Console.WriteLine(this.ToString()); } |
이제 대리자를 하나 선언해 봅시다.
delegate void EducationDele(int tcnt); |
ildasm유틸리티로 il코드를 확인해 보면 EducationDele형식이 MulticastDelegate에서 파생되었고 봉인된 클래스임을 확인할 수 있습니다.
그리고, 생성자와 BeginInvoke, EndInvoke,Invoke메서드가 있는 것을 확인할 수가 있습니다.
사용하는 코드를 작성해 봅시다.
먼저, 학생 개체 인스턴스를 하나 만듭시다.
Stu s = new Stu(3, "홍길동"); |
그리고, 학생의 Study메서드를 위임받은 대리자 개체 인스턴스를 하나 만들께요.
EducationDele del1 = new EducationDele(s.Study); |
대리자는 마치 함수처럼 호출이 가능합니다.
del1(4); |
학생의 Play메서드를 위임받은 대리자 개체 인스턴스도 하나 만들고 대리자 del2의 Invoke메서드를 호출합시다.
EducationDele del2 = new EducationDele(s.Play); del2.Invoke(5); |
그리고, 마지막으로 두개의 대리자를 + 연산자를 통해 del3에 대입하고 호출해 봅시다.
EducationDele del3 = del1 + del2; del3(10); |
실행해 보면, del1을 통해 Study를 위임받아 동작하는 것과 del2를 통해 Play를 위임받아 동작하는 것, 그리고, del3를 통해 Study와 Play를 둘 다 위임받아 동작하는 것을 확인하실 수가 있습니다.
Ildasm 유틸을 통해 다시 il코드를 확인해 보면 Study를 위임 받은 delegate도 내부적으로 Invoke를 호출하는 것으로 작성됨을 알 수 있습니다.
그리고, 내려서 보시면 System.Delegate스코프에 Combine메서드를 통해 두 개의 대리자가 결합되는 것을 알 수 있습니다.
그리고, 대리자는 암시적 봉인 클래스가 된다고 했고 il코드로 확인을 했었는데 다시 확인해 봅시다.
//class Example : EducationDele //{ //} |
컴파일을 해 보면 “sealed 형식에서 파생할 수 없습니다.”라는 오류메세지가 나오는 것을 알 수 있습니다.
이번에는 대리자의 비동기 호출에 대해 살펴보도록 합시다.
비동기적으로 동작하는 것을 인지할 수 있게 학습을 1회 할 때마다 화면에 출력하고 Thread클래스의 static메서드인 Sleep을 가지고 1초를 멈추는 것을 반복하게 수정을 하세요.
Thread클래스는 System.Threading; 네임스페이스를 추가하시면 됩니다.
public void Study(int tcnt) { int i = 0; for (i = 0; i < tcnt; i++) { Console.WriteLine("{0}시간중 {1}시간 학습 중...", tcnt, i); Thread.Sleep(1000); Iq += tcnt; } } |
그리고, Main메서드에 가서 다음 부분을 주석 처리를 하고
//del1(4); //EducationDele del2 = new EducationDele(s.Play); //del2.Invoke(5); //EducationDele del3 = del1 + del2; //del3(10); |
del1을 가지고 BeginInvoke메서드를 호출합시다. 입력 매개변수는 delegte 정의에 명시한 입력 매개변수가 앞에 오고 그 뒤에 AsyncCallBack이 오고 마지막 인자는 AsyncCallBack에 전달한 인자를 넣으면 됩니다.
del1.BeginInvoke(10, EndEducation, del1); |
AsyncCallBack의 인자는 리턴 형식이 void이고 입력 인자가
IAsyncResult입니다.
static public void EndEducation(IAsyncResult iar) { EducationDele dele = iar.AsyncState as EducationDele; if (dele != null) { dele.EndInvoke(iar); } Console.WriteLine("교육 끝"); } |
Iar의 멤버 속성 AsyncState를 전달한 인자 형식으로 형식변환을 하고
유효한 경우에 비동기 호출 작업을 완료하면 됩니다.
확인을 용이하게 하기 위해 Main메서드에도 for문을 이용하여 루프 카운트를 출력하고 0.3초를 멈추는 것을10회 반복합시다.
for (int i = 0; i < 10; i++) { Console.WriteLine("Main : {0}", i); Thread.Sleep(300); } Console.ReadKey(); } |
메인 메서드가 종료되면 프로그램이 끝이나니 Console.ReadKey메서드를 호출하여 키를 눌러야 종료되게 합시다.
동작을 시켜보면 Main에서의 작업과 대리자에 위임한 작업이 서로 비동기적으로 수행함을 알 수 있습니다.
이상으로 대리자에 대한 강의를 마치도록 하겠습니다.
'Programming > .NET' 카테고리의 다른 글
[.NET] Delegate 실습, 크로스 스레드 해결방법 (0) | 2015.05.28 |
---|---|
[.NET] Assembly - 2 (0) | 2015.05.19 |
[.NET] DataRepeater Control (0) | 2015.05.19 |
[.NET] Reflection (C#) (0) | 2015.05.19 |
[.NET] DataAdapter 생성 (0) | 2015.05.19 |