Creative Commons License

Microsoft .NET

닷넷!시작하기
닷넷! Ver 2.0~
닷넷!스킬업
웹개발
윈폼개발
실용모듈개발
Tip & Tech
하루 한 문법

Microsoft .NET 개발자들을 위한 공간입니다. 기초강의에서 부터 고급 기술 정보 및 팁등을 다루도록 하겠습니다.

.

닷넷!시작하기

Microsoft. NET 을 시작하는 분들을 위한 강좌입니다. 주로 기초적인 내용과 때론 기본적인 내용을 다룹니다

[C# 기초강좌] 16. C# 델리게이트(delegate)

작성자 : 박종명
최초 작성일 : 2010-07-02 (금요일)
최종 수정일 : 2010-07-02 (금요일)
조회 수 : 8985

델리게이트(delegate)는 함수를 참조하는 형식(Type) 입니다"

 

안녕하세요. 박종명입니다. 닷넷 열여섯 번째 강좌를 진행하도록 하겠습니다

보름간의 휴가를 마치고 새로운 회사에 새로운 맘으로 새 출발(?) 하고 있습니다

그간 카페 이름도 바뀌고 스터디도 생기고 하는 등 많은 변화가 있었네요. 저도 앞으로 더 열심히 활동 하겠습니다

 

이번 강좌는 닷넷의 델리게이트(delegate)에 대해 살펴 보겠습니다

 

델리게이트의 한글 번역은 대리자입니다. delegate의 사전적 의미도 위임과 관련이 있습니다

이처럼 이름에서 유추해 보면 뭔가를 대신하여 처리하는 역할을 하는 것이다.. 라고 유추할 수 있겠네요

지금부터 자세히 알아보도록 하겠습니다

 

 

우리 카페 회원님들은 대체로 C에 가까우셔서 함수 포인터에 대하 아실 거라 판단됩니다

닷넷의 델리게이트는 바로 C, C++, Pascal 에서 지원하는 함수 포인터와 개념적으로 유사합니다

 

델리게이트는 특정 클래스의 함수(메서드)에 대한 참조를 캡슐화한 데이터 구조입니다

델리게이트에 메서드가 할당되면 델리게이트를 통해 메서드 호출을 수행할 수 있는 것이죠

 

즉 메서드 호출에 대한 참조를 가진 델리게이트를 통해 메서드를 간접적으로 호출하는 기법을 제공해 줍니다.

 

Delegate 클래스

Delegate 클래스는 닷넷 프레임워크에 정의되어 있습니다.

다만 개발자가 명시적으로 이 클래스를 상속받을 수는 없으며 delegate라는 키워드를 통해 델리게이트를 구현하게 됩니다. 이렇게 하면 컴파일러에 의해 자동으로 Delegate 로부터 상속받은 클래스가 만들어 집니다

 

MulticastDelegate 클래스

사실 개발자가 delegate 키워드를 통해 생성한 델리게이트 클래스는 앞서 설명한 Delegate 클래스를 상속한

MuliticastDelegate에서 상속됩니다. 즉 델리게이트의 최상위 부모는 Delegate이며 바로 위 부모는
MulitcastDelegate
클래스가 되는 것입니다.

MulitcastDelegate 클래스는 여러 메서드를 호출하기 위한 호출 목록을 가진 특수한 클래스입니다.
즉 하나의 델리게이트에 여러 함수를 메서드가 호출 될 수 있도록 하는 연결된 목록을 가집니다

 

이는 이벤트에서 유용하게 이용되는데요. 하나의 이벤트에 여러 메서드를 참조하도록 하여 한번 호출에 여러 메서드가 호출되도록 하는 경우 유용합니다 (이벤트에서는 참조되는 메서드를 += 계속 추가하고 -= 로 제거할 수 있습니다)

 

그리고 굳이 이벤트가 아니더라도 델리게이트에서도 + 연산자를 이용하여 멀티캐스트가 될 수 있습니다

MyDelegate a,b,c,d

c = a + b  : c 델리게이트에 a, b 델리게이트를 할당하여 멀티캐스트가 되게 합니다

d = c ? a  : d 델리게이트는 c 델리게이트에서 a 델리게이트를 제거하도록 합니다

 

 

델리게이트 사용하기

그럼 실제 델리게이트를 사용하는 방법에 대해 간단히 살펴 보겠습니다

델리게이트 역시 클래스이기 때문에 클래스의 정의, 선언, 생성과 유사한 형태를 지닙니다

 

델리게이트 정의

delegate void MyDelegate(string s);

delegate 키워드를 통해 델리게이트를 정의하게 되면 컴파일러에 의해 MulticastDelegate 클래스로부터 상속받은
새로운 클래스가 정의됩니다.

 

델리게이트 선언

MyDelegate myDelegate;

앞서 정의한 델리게이트 타입(MyDelegate) 으로 참조 변수를 선언합니다

 

델리게이트 인스턴스 생성

myDelegate = new MyDelegate(Test);

그리고 델리게이트 인스턴스를 생성합니다. 이때 생성자 매개변수로 간접 호출 대상 메서드 정보를 전달합니다.

 

델리게이트 호출

myDelegate("hi");

이후 델리게이트를 마치 메서드 호출과 같은 방식으로 다룰 수 있습니다

이렇게 호출 하면 Test라는 메서드가 델리게이트에 의해 간접적으로 호출되는 것입니다

 

그럼 전체 코드를 보겠습니다

class Program{

   delegate void MyDelegate(string s);

       

   static void Main(string[] args){

       MyDelegate myDelegate = new MyDelegate(Test);

       myDelegate("hi");

  }

 

   public static void Test(string s){

        Console.WriteLine(s);

   }

}

 

 

델리게이트와 메서드 시그너처(signature)

델리게이트가 메서드에 대한 간접참조를 지원하기 때문에 메서드의 선언과 델리게이트의 선언은 서로 호환되어야 합니다. 위 예에서 보면 Test라는 메서드는 문자열 타입의 입력 매개변수 하나와 void 반환으로 선언되어 있습니다

따라서 델리게이트 역시 이러한 ,출력 매개변수 정보(타입 및 개수)가 동일해야 합니다

메서드 선언:         public static void Test(string s)

델리게이트 선언:  delegate void MyDelegate(string s);

 

 

기타 델리게이트 특징

 

인스턴스 메서드도 델리게이트로 참조할 수 있습니다

앞서 살펴본 예제에서는 정적(static)메서드에 대한 내용이었지만 델리게이트는 인스턴스 메서드에 대해서도 참조를
할 수 있습니다

 

MyClass myClass = new MyClass();//참조할 메서드가 정의된 클래스의 인스턴스(객체)

MyDelegate myDelegate = new MyDelegate(myClass.Test);           

myDelegate("hi");

 

메서드의 접근 한정자

델리게이트가 메서드에 대한 참조 가질 때 해당 메서드가 델리게이트에서 접근 가능한 한정자를 가져야 합니다.
즉 외부 클래스에 정의된 메서드를 참조하고자 할 경우 public (혹은 internal) 로 정의되어야만
델리게이트에서
해당 메서드를참조할 수 있습니다. 만일 private protected 로 구현되어 있다면 오류가 발생합닏. .. 굳이 설명할 필요 없는 당연한 말이죠. 일반적인 접근 한정자와 동일한 개념입니다

 

 

델리게이트와 이벤트

델리게이트는 닷넷의 이벤트 기반 구조에 이용됩니다

자바에서는 이벤트 수신기 클래스를 구현하여 이벤트를 지원하는 반면 닷넷에서는 델리게이트 이용하여 이벤트를 처리합니다. 이벤트에 대해서는 다음에 알아 보도록 하겠습니다

 

 

델리게이트 이용 사례

델리게이트라는 것을 어떤 상황에 이용하면 장점을 누릴 수 있을까요?

앞서 살펴본 예제로는 언뜻 알기 힘듭니다. 그냥 메서드 호출 하면 될 것을 굳이 델리게이트를 통해 간접호출 하는
이유가 뭘까요? 분명 장점이 있으니 언어차원에서 제공해 주겠죠

 

닷넷에서 일단은 이벤트 구조의 근간이 되는 특징적 요소가 하나의 장점이라 하겠네요

 

나아가 구현적 장점이라 함은,

- 서로 다른 일을 하는 서로 다른 메서드(단 동일한 시그너처)를 동일한 방법으로 호출할 수 있다.

- 델리게이트를 이용하여 실행 시 메서드 호출을 결정할 수 있다

 

정도로 요약할 수 있겠습니다

 

이러한 장점을 느낄 수 있는 MOC에 제공된 데모를 보겠습니다

 

시나리오>

우선 구현하고자 하는 소프트웨어의 환경 제약 조건을 보겠습니다

펌프를 제조하는 회사가 여러 곳 있습니다. 각 제조사는 서로 다른 명령어를 사용합니다

어떤 제조사는 펌프를 시작하는 명령으로 Start 를 사용합니다.

또 다른 제조사는 펌프를 시작하는 명령으로 Go 를 사용합니다

 

이제 이 두 펌프를 가져다 사용하는 프로그램을 작성해야 합니다.

서로 다른 제조사가 만든 펌프 프로그램을 가져와서 적절히 시작할 수 있게 프로그램을 작성해야 합니다

시작 명령어가 서로 다르기 때문에 다음과 같이 코드를 작성할 수 있겠네요

 

//제조사1의 펌프

public class Pump1{

   public void Start(){

        Console.WriteLine("Pump1 - 시작되었습니다");

    }

}

//제조사2의 펌프

public class Pump2{

    public void Go(){

        Console.WriteLine("Pump2 - 시작되었습니다");

    }

}

 

//두 회사의 펌프를 가져다 쓰는 프로그램

public class PumpManager{

    private ArrayList al = new ArrayList(); //펌프 저장을 위한 컬렉션

 

    public void Add(object o){

        al.Add(o); //각 제조사 펌프를 추가한다

    }

 

    //모든 펌프를 시작한다. 펌프 제조사를 파악한 후 적절한 명령을 내려야 한다

    public void AllStart(){

        foreach (Object o in al){

            if (o is Pump1) //만일 제조사가 Pump1 이면..{

                ((Pump1)o).Start();

             }

             else if (o is Pump2) //만일 제조사가 Pump2 이면..{

                ((Pump2)o).Go();

            }

        }

    }

}

 

코드에서와 같이 해당 펌프 제조사를 파악한 후 적절한 시작명령(Start or Go)를 호출해야 합니다

이 때 다른 펌프 제조사가 또 추가 된다면 if 문은 또 추가되어야겠지요

이것을 델리게이트로 변환해 보겠습니다

 

//제조사1의 펌프

public class Pump1{

    public void Start(){

        Console.WriteLine("Pump1 - 시작되었습니다");

    }

}

//제조사2의 펌프

public class Pump2{

    public void Go(){

        Console.WriteLine("Pump2 - 시작되었습니다");

    }

}

 

public delegate void StartPumpCallBack(); //펌프 시작을 간접 호출할 Deleate 선언


//
두 회사의 펌프를 가져다 쓰는 프로그램   

public class PumpManagerUsingDelegate{

    private ArrayList al = new ArrayList();

    public void Add(StartPumpCallBack callBack) // Delegate 변수를 매개변수 받는다

    {

        al.Add(callBack);

    }

    public void AllStart(){

        foreach (StartPumpCallBack scb in al){

// 펌프의 시작을 Delegate 로 한다.
//(
제조사에 상관없이 동일한 방법으로 펌프를 시작할 수 있다)

        scb();

        }

    }

}

 

두 펌프의 시작 메서드를 참조하는 델리게이트를 생성하고 해당 델리게이트로 메서드를 호출하고 있다

제조사의 시작 명령이 다르지만 동일한 델리게이트 이름으로 메서드 호출이 가능해 졌습니다

 

 

아래는 Main 코드 입니다

static void Main(string[] args){

    Pump1 p1 = new Pump1();

    Pump2 p2 = new Pump2();

 

    #region  Delegate 사용하지 않는 경우

    //PumpManager pm = new PumpManager();

    //pm.Add(new p1());

    //pm.Add(new p2());

    #endregion

 

    #region  Delegate 사용하는 경우

    PumpManagerUsingDelegate pm = new PumpManagerUsingDelegate();

    pm.Add(new StartPumpCallBack(p1.Start));

    pm.Add(new StartPumpCallBack(p2.Go));

    #endregion

 

    pm.AllStart();

}

 

참고로 이 시나리오에서 인터페이스를 사용해도 유사한 효과를 누릴 수 있습니다

인터페이스와 델리게이트의 사용 지침은 msdn에서 자세히 설명해 주고 있네요. 아래 내용 참고하세요

 

클래스 디자이너는 대리자와 인터페이스 둘 모두를 통해 형식의 선언과 구현을 분리할 수 있습니다. 특정 인터페이스는 모든 클래스나 구조체에서 상속하고 구현할 수 있습니다. 대리자는 메서드 시그니처가 대리자의 메서드 시그니처와 일치하는 모든 클래스의 메서드에 대해 생성될 수 있습니다. 인터페이스나 대리자 메서드를 구현하는 클래스에 대한 정보가 전혀 없는 개체에서 인터페이스 참조나 대리자를 사용할 수 있습니다. 대리자와 인터페이스는 이와 같은 점에서 유사하기 때문에 대리자와 인터페이스 중 어느 쪽을 사용할지 결정할 때는 다음과 같은 지침을 따라야 합니다.

다음과 같은 경우에 대리자를 사용합니다.

·         이벤트 디자인 패턴을 사용하는 경우

·         정적 메서드를 캡슐화해야 하는 경우

·         메서드를 구현하는 개체에 대한 다른 속성, 메서드 또는 인터페이스에 호출자가 액세스할 필요가 없는 경우

·         쉽게 작성하려는 경우

·         메서드에 대한 여러 구현이 클래스에 필요한 경우

다음과 같은 경우에 인터페이스를 사용합니다.

·         호출할 수 있는 관련 메서드의 그룹이 있는 경우

·         클래스에 메서드의 구현이 하나만 필요한 경우

·         인터페이스를 사용하는 클래스에서 이 인터페이스를 다른 인터페이스나 클래스 형식에 캐스팅하려는 경우

·         비교 메서드의 경우와 같이 구현하려는 메서드가 클래스의 형식이나 동일성 여부와 관련되어 있는 경우

 

 

 

공변성(Covariance)과 반공변성(Contravariance)

마지막으로 델리게이트의 공변성과 반공변성에 대해 알아 보겠습니다

 

앞서 델리게이트 선언과 메서드 시그너처는 동일해야 한다고 했습니다

그러나 상속과 관련해서는 조금 더 융통성을 발휘할 수 있습니다

 

즉 메서드의 반환타입이 객체이고 이 객체가 상속관계에 있을 경우 델리게이트의 출력 매개변수 타입과 입력 매개변수 타입이 메서드에서 정의한 것과 다를 수 있다는 것입니다

 

이때 공변성은 델리게이트의 반환형식이 메서드의 반환형식의 부모 클래스인 경우를 말하며

반공변성은 델리게이트의 매개변수형식이 메서드의 매개변수 형식의 자식 클래스인 경우를 말합니다

 

 

이상으로 델리게이트 강좌를 마치도록 하겠습니다

(?) 휴가와 급변한 환경에 적응하다 보니 글이 좀 늦었네요.

그리고 대체로 글이 매끄럽게 진행되지 않네요.. 이해 바랍니다 ~

 

그럼. 즐거운 한 주 되세요 ~~

이름
비밀번호
홈페이지
WT <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요