2013년 9월 8일 일요일

닷넷, .NET, C# 대리자(delegate), C#강좌교육, 닷넷교육강좌
 
델리게이트(Delegate)는 C의 함수 포인터와 비슷한 역할을 한다. 즉, 자기 자신이 실제로 하는 일은 없고, 단지 자기가 가리키고 있는 메서드(함수)를 호출하는 역할을 하는 것이다.
 
결국 델리게이트(Delegate)는 함수에 대한 참조를 저장 하는 것인데 이렇게 함수에 대한 참조를 가리키고 있다는 것으로 인해 불가능한 작업등이 가능해진다. 예를 들면 Delegete를 다른 함수의 인자로 넘겨주게 되면 그 함수는 델리게이트가 보내 주는 함수의 참조를 이용하여 실행 시점에 호출될 함수를 결정 할 수 있는 것이다.
 
대리자의 선언은 일반 함수의 선언과 비슷하나 함수 본문이 없고 delegate 라는 키워드를 사용한다는 점이 다르다. 델리게이트는 쓰레드와 이벤트에서 주로 이용 할 수 있다는 것은 참고로 알아 두자.
 
아래의 코드를 보도록 하자…
int i = int.Parse(“3746”);
일반적으로 대부분의 메서드들이 인자 값을 받아 어떤 일 처리를 하게 된다. 위의 코드에서 Parse()메서드는 string형의 인자를 받아 int형으로 바꾸어 주는 역할을 하는데, 대부분의 메서드들이 인자를 받는 이유는 왜일까?
메서드들이 인자를 받는 이유는 처리해야 할 값을 runtime시에만 알 수 있기 때문이다. 예를 들어 숫자를 정렬하는 메서드를 만들었다고 가정하자  사용자가 숫자 몇에서 몇까지를 정렬하기를 원하는지 알 수가 없다. 따라서, 나중에 그 값을 입력 받도록 처리 하기 위해 인자를 쓰게 되는 것이다.
Delegate도 이와 같은 개념으로 생각하시면 되며 단지 다른 점이 있다면, value형인 매개변수 대신에 메서드를 인자로 받는다는 것이다. 그런데, 메서드를 인자로 받아야 할 상황들이 있을까요? 잠시 후, 예제를 보면 그런 상황들이 있다는 것을 알게 될 것이다.

Delegate 선언 문법은 다음과 같다.
①delegate ②void ③VoidOperation(④uint X);
 
①은 delegate임을 선언, ②는 리턴형 ③은 delegate의 사용자 정의 ④는 입력 받는 인자의 형 및 변수 이다. 사용형식은 일반 메서드를 선언하는 것과 아주 흡사 한데 다른 점이 있다면 delegate라는 키워드를 먼저 쓰는 것과 구현부({ … })가 없다는 것이다. 구현부가 없는 이유는 이미 설명 드린 것과 같이 delegate 자신은 아무것도 하지 않고, 자신이 참조하고 있는 메서드(함수)만 호출해 주기 때문 이다.
Delegate를 선언하는 것은 새로운 클래스를 생성하는 것과 같다. 형식은 데이터 타입처럼 쓰이지만, 실제로 System.Delegate 클래스에서 파생되는 새로운 객체를 생성한다.
 
 
 
아래의 예제를 보자.[예제1]
using System;
public class Delegate1
{
      private delegate string GetString();
 
      public static void Main(string[] args)
      {
            int x = 30;
            GetString myMethod = new GetString(x.ToString);
            Console.WriteLine("문자열 : {0}", myMethod());
      }
}
 
Delegate를 사용할 때 주의할 점은 delegate가 참조하는 메서드(여기서는 ToString) 의 인자 및 리턴형이 선언된 delegate와 같아야 한다는 것이다. 예제 1에서도 볼 수 있듯이, ToString() 메서드는 인자가 없고, string형을 리턴 한다. 따라서 선언된 delegate도 인자가 없이 string형을 리턴 하도록 선언되어 있지만 delegate는 참조하는 메서드가 어떤 타입이든상관 하지 않는다. static이든 메서드(함수) 객체이든 간에 관계없다. 즉, 참조하는 메서드의 인자형, 개수, 리턴 타입만 같으면 문제가 없다는 것 이다.
 
[예제2]
using System;
 
class MathOperation
{
      public static double MultipleByTwo(double value)
      {
            return value * 2;
      }
 
      public static double Square(double value)
      {
            return value * value;
      }
}
 
delegate double DoubleOp(double x);
 
class DelegateTest2
{
      static void Main(string[] args)
      {
            //Delegate를 배열로 지정
            DoubleOp[] operation =
                  {
                        new DoubleOp(MathOperation.MultipleByTwo),
                        new DoubleOp(MathOperation.Square)
                  };
           
            for(int i=0; i<operation.Length; i++)
            {
                  Console.WriteLine("operation[{0}] 호출:", i);
                  CallDelegate(operation[i], 5.0);  //Delegate를 다른 메소드의 인자로 넘긴다.
                  Console.WriteLine();
            }
      }
 
      static void CallDelegate(DoubleOp func, double value)
      {
            //넘겨받은 Delegate를 실행 한다.
            double ret = func(value);
            Console.WriteLine("입력된 값은 {0}이고 결과는 {1} 이다", value, ret);
      }
}
 
위의 예제에서 CallDelegate라는 메서드를 호출할 필요 없이 곧바로 delegate가 참조하는 메서드를 실행 하려면 다음과 같이하면 된다.
operations[i](2.0);
 
Delegate가 꼭 필요 한 경우
 
아래와 같은 버블정렬이 있다고 하자.
 
[예제3]
for (int i=0; i<sortArray.Length; i++)
{
     for (int j=0; j<i; j++)
     {
          ①if (j > i) // 문제의 소지가 있는 부분
          {
               int temp = sortArray[i]; // i 와 j 바꾸기
               sortArray[i] = sortArray[j];
               sortArray[j] = temp;
          }
     }
}
 
만약 이 버블정렬의 데이터가 위와 같은 숫자가 아니라 객체라면 어떻게 될까? 현재와 같은 구조에선 곤란하다. 이는 사용자가 어떤 식으로 클래스나 구조체 등을 정의해 놓았는지 알 수가 없기 때문이다. 사용자에게 여러분이 만든 버블정렬 컴포넌트를 판매한다고 가정한다면, 각 사용자를 위해 다른 프로그램을 만든다는 건 말이 안 되는 이야기 이다.이럴 때 필요한 것이 바로 delegate다!!
 
[예제4]
using System;
//우리가 배포하는 버블정렬 컴포넌트
class BubbleSorter
{
      //RunTime에 수행할 함수를 isConvert 라는 Delegate를 통해서 받는다.
      static public void Sort(object [] sortArray, CompareOp isConvert) //①
       {
             for (int i=0; i<sortArray.Length; i++)
             {
                   for (int j=0; j<i; j++)
                   {
                         if (isConvert(sortArray[i], sortArray[j])) //②
                          {
                               object temp = sortArray[i];
                               sortArray[i] = sortArray[j];
                               sortArray[j] = temp;
                          }
                   }
             }
       }
}
 
//사용자 정의 클래스
class Employee
{
      private string name;
      private decimal salary;
 
      public Employee(string name, decimal salary)
      {
            this.name = name;
            this.salary = salary;
      }
 
      public override string ToString()
      {
            return string.Format(name + ", {0:C}", salary);
      }
 
      //사용자 정의 클래스 비교 메서드
      public static bool isConvert(object obj1, object obj2) //③
       {
             Employee e1 = (Employee) obj1;
             Employee e2 = (Employee) obj2;
             return (e1.salary > e2.salary) ? true : false;
       }
}
 
delegate bool CompareOp (object lhs, object rhs);
 
class DelegateClass
{
      static void Main(string[] args)
      {
            Employee [] employees =
                             {
                             new Employee("순돌이", 5000),
                             new Employee("순딩이", 1500),
                            new Employee("차돌이", 3000),
                             new Employee("공돌이",1000)
                             };
            CompareOp EmployeeCompareOp = new CompareOp(Employee.isConvert);
            BubbleSorter.Sort(employees, EmployeeCompareOp);
 
            for (int i=0; i<employees.Length; i++)
                  Console.WriteLine(employees[i].ToString());
      }
}
 
 
 
①을 보시면 우리가 배포할 버블정렬 컴포넌트를 다시 편집한 것을 보실 수 있는데 , 컴포넌트는 두 개의 인자를 받는다, 첫번째는 정렬하게 될 object(즉, 모든 데이터 타입을 받을 수 있습니다. Object는 가장 상위클래스이기 때문이다.) 타입의 데이터이고, 두번 째 인자는 CompareOp delegate 이다. 다시 정리하면, object형 데이터와 delegate를 인자로 받는 버블정렬 컴포넌트 인 것이다.
②에서는 이전 소스코드에서 문제가 되었던 부분을 수정한 것으로 단지 숫자만 비교할 수 있게 만든 것이 아니라 delegate를 통해서 사용자 정의 비교 메서드를 호출하여 값을 비교하는 것이다. 여기서는 isConvert라는 delegate를 사용하고 있는 것이다.
③은 사용자가 정의한 Employee라는 클래스를 비교하는 메서드
④에서는 CompareOp delegate가 ③에서 정의한 사용자 정의 비교 메서드를 참조하도록 하고 있다.
⑤에서는 employees라는 배열 데이터와 EmployeeCompareOp라는 delegate를 Sort() 메서드의 인자로 넘겨 정렬하고 있다.
위의 예제를 보시면, 이제 우리가 만든 버블정렬 컴포넌트는 어떤 상황에도 문제없이 사용될 수 있다는 것을 알 수 있다. 버블정렬 컴포넌트의 핵심기술(?)은 두 데이터의 값을 비교하는 것인데, 이 부분을 delegate로 바꿈으로써, 사용자가 정의할 수 있도록 만든 것이다. 그 만큼 컴포넌트 유연성 혹은 확장성이 확대된 것이다.
 
우리는 delegate도 배열로 선언되어 사용될 수 있음을 이전전 예제에서 보았다. 하지만, 꼭 배열로 선언하지 않아도 하나의 delegate에 여러 개의 메서드를 넣을 수가 있는데 다음과 같이 쓸 수 있다.
delegate void DoubleOp(double value);
class MainEntryPoint
{
     static void Main(string[] args)
     {
          DoubleOp operations = new DoubleOp(MathOperations.MultiplyByTwo);
          operations += new DoubleOp(MathOperations.Square);
여러분이 원하시면, 다음과 같이 하는 것도 똑 같은 효과를 낸다.
DoubleOp operation1 = new DoubleOp(MathOperations.MultiplyByTwo);
DoubleOp operation2 = new DoubleOp(MathOperations.Square);
DoubleOp operations = operation1 + operation2;
 
 
 
C#에서의 이벤트 모델
이 이벤트 모델은 전체 Visual C# 의 가장 일반적인 모델이며 정규화된 모델이기 때문에 여러분들은 이 모델 하나만을 숙지하시면 C# 전체의 이벤트 모델을 이해하셨다고 보셔도 좋다. 이번에는 윈도우 폼을 생성한 뒤 사용자가 폼을 클릭했을 때 폼의 클릭 (Click) 에 해당하는 이벤트의 처리과정을 알아볼 것이다.
 
1단계: GUI 이벤트를 테스트하기 위한 윈도우 폼 만들기
윈도우 이벤트 모델을 이해하기 전에 먼저 Form 을 하나 만들고 그 폼을 화면상에 디스플레이하는 프로그램을 작성하도록 하겠다 .
 
&
 EventFormTest.cs
 U 윈도우 폼 만들기

using System;
using System.Windows.Forms;
public class  EventForm : Form{
}  //class
public class  EventFormTest{
   static  void  Main() {
    Application.Run( new  EventForm());
  }  //main
}  //class

C:\C#Example\12>csc EventFormTest.cs
C:\C#Example\12>EventFormTest

 
 
위와 같이 간단하게 윈도우 폼을 하나 생성했다 .
 
2.
화재 , 도난과 교통사고 등의 이벤트가 존재하는 것처럼 윈도우 폼 에도 미리 등록된 이벤트가 존재 하는데 . 이러한 이벤트는 여러분이 필요할 때 찾아서 사용하시면 된다 .
 

그림 12-3 VS.NET IDE 에서 보여지는 이벤트
 
비쥬얼 툴 내에서 소스 코드를 작성할 때 폼에 대한 참조값을 사용하면 위와 같은 드롭다운 정보를 자동으로 보여주며  이 때 번개모양을 한 것이 바로 이벤트에 해당한다. 즉 , 미리 등록된 이 이벤트가 존재한다는 것이다 . 아래의 표는 Form 에서 사용할 수 있는 이벤트들의 목록 이다.
 
Form 에서 사용할 수 있는 이벤트들

Activated, BackColorChanged, BackgroundImageChanged, BindingContextChanged, CausesValidationChanged, ChangeUICues, Click, Closed, Closing, ContextMenuChanged, ControlAdded, ControlRemoved, CursorChanged, Deactivate, Disposed, DockChanged, DoubleClick, DragDrop, DragEnter, DragLeave, DragOver, EnabledChanged, Enter, FontChanged, ForeColorChanged, GiveFeedback, GotFocus, HandleCreated, HandleDestroyed, HelpRequested, ImeModeChanged, InputLanguageChanged, InputLanguageChanging, Invalidated, KeyDown, KeyPress, KeyUp, Layout, Leave, Load, LocationChanged, LostFocus, MaximizedBoundsChanged, MaximumSizeChanged, MdiChildActivate, MenuComplete, MenuStart, MinimumSizeChanged, MouseDown, MouseEnter, MouseHover, MouseLeave, MouseMove, MouseUp, MouseWheel, Move, Paint, ParentChanged, QueryAccessibilityHelp, QueryContinueDrag, Resize, RightToLeftChanged, SizeChanged, StyleChanged, SystemColorsChanged, TabIndexChanged, TabStopChanged, TextChanged, Validated, Validating, VisibleChanged
 
표 12-4 Form 에 사용할 수 있는 이벤트들
 
이러한 이벤트들은 여러분이 폼을 띄웠을 때 작동하는 것도 있으며 작동하지 않는 것도 있습니다. 이 많은 이벤트들이 하나의 모델로 동작한다는 것은 아주 신기한 일이다 . 즉 , 어떠한 이벤트가 등록되었는지를 확인하고 그 이벤트를 수신하는 측과 연결해 주는 것이다 . 이 중에서 우리는 Click 이벤트의 처리기 를 만들어 볼 것이다.
 
 
3단계: Click 이벤트 처리기(Event Handler) 만들기
Click 이벤트의 처리기를 만드는 방법은 일반적인 메서드를 만드는 방법과 같다 . 하지만 , 여기서 주의해야 하는 것이 바로 메서드의 매개변수입니다 . 메서드를 만들 때의 형식은 다음과 같다 .
 
이벤트 처리기 (EventHandler) 의 형태

private void ClickReceive(object sender, EventArgs e){
}
 
표 12-5 이벤트 처리기 (EventHandler)
 
매개변수 중 Object sender 는 메시지가 어디서 발생하는지 발생된 곳의 참조값을 의미하며 , EventArgs e 는 이벤트의 데이터를 담고 있는 매개변수 이다 . 이 두가지의 매개변수는 바꿀 수 없으며 항상 위와 같은 규칙으로 사용해야 한다 . 이벤트 처리기까지 포함한 소스는 다음과 같다 .
 
&
 EventFormTest.cs
 U 윈도우 폼에서 이벤트 수신할 메서드 추가

using System;
using System.Windows.Forms;
public class  EventForm : Form{
   private  void  ClickReceive(object sender, EventArgs e){
    MessageBox.Show( "Hello World!" );
  }
}  //class
public class  EventFormTest{
   static  void  Main() {
    Application.Run( new  EventForm());
  }  //main
}  //class
 
 
위의 예제에서는 이벤트 처리기만을 갖추었을 뿐 이벤트 처리기를 Click 이벤트에 등록해 주지 않았 으므로 다음에서 이벤트 처리기를 이벤트에 등록 해 보자
 
 
4단계: Form의 Click 이벤트에 이벤트 처리기 등록하기
이벤트 처리기를 등록하기 위해서는 Delegate 형태로 만들어서 등록해야 한다 . 물론 , 등록은 Click 이벤트에 해야 하고 , Click 이벤트에 등록할 때는 반드시 Delegate 형태로 만들어서 등록을 해야 한다 . 이 때 사용되는 Delegate 가 바로 EventHandler 인데 앞에서 만든 이벤트 처리기를 EventHandler 로 만든 후 , EventHandler 를 등록하게 된다 .
 
그런데 앞에서 이벤트 처리기를 만들 때 왜 매개변수 2 개를 맞추어 준 것일까? . 바로 EventHandler 가 아래와 같은 모양을 하고 있기 때문에,  EventHandler 델리게이트와 메서드를 맞추어 주기 위해서 였다 .
 
public delegate void EventHandler(object sender,EventArgs e);
 
다음은 EventHandler 를 만들어서 Click 이벤트에 등록하는 완성된 예제다 .
 
&
 EventFormTest.cs
 U 윈도우 폼에서 이벤트 수신할 메서드 추가

using System;
using System.Windows.Forms;
public class  EventForm : Form{
   public  EventForm(){
     this.Click +=   new  EventHandler(ClickReceive);  // 이벤트 처리기 등록하는 부분
   }
   private  void  ClickReceive(object sender, EventArgs e){
    MessageBox.Show( "Hello World!" );
  }
}  //class
public class  EventFormTest{
   static  void  Main() {
    Application.Run( new  EventForm());
  }  //main
}  //class
 



오라클자바커뮤니티 실전 강좌 - 개인80%환급

C#4.0, ADO.NET, Network 프로그래밍 4일 32시간   09-24
C#,ASP.NET마스터 8일 56시간   09-25
ASP.NET4.0 MVC 프로그래밍 4일 32시간   09-30
C#,ASP.NET마스터 18일 54시간   09-25
ASP.NET4.0 MVC 프로그래밍 11일 33시간   09-26
C#4.0, ADO.NET, Network 프로그래밍 11일 33시간   09-30
C#,ASP.NET마스터 8일 56시간   09-14
C#4.0, ADO.NET, Network 프로그래밍 4일 32시간   09-28
ASP.NET4.0 MVC 프로그래밍 4일 32시간   09-28 

댓글 없음:

댓글 쓰기