Silverlight를 설치하려면 여기를 클릭합니다.*
Korea 대한민국변경|Microsoft 전체 사이트
MSDN
|개발자 센터
MSDN Home   MSDN Home
MSDN 홈 > MSDN Magazine > 2001년 기사 > 위임 소개(2부)
위임 소개(2부)
Jeffrey Richter 저
두달 전에 Microsoft® .NET Framework 버전의 콜백 메서드 위임에 대한 소개를 하였습니다. 이 컬럼에서 위임 선언으로 컴파일러가 System.MulticastDelegate에서 파생된 클래스를 어떻게 생성하는지와 이 클래스의 각 인스턴스가 콜백 메서드가 동작해야 하는 개체를 지정하는 두 개의 전용 필드(_target 및 _methodPtr)를 어떻게 포함하는지에 대해 설명했습니다. 또한, 위임의 연결 목록 체인을 유지하기 위해 사용되는 세 번째 전용 필드인 _prev를 소개했습니다. 이번 달에는 이 _prev 필드에 대해 좀 더 다루고 연결 목록 위임 체인이 어떻게 관리 및 사용되는지에 대해 자세히 설명합니다.


위임의 역사
(System.Delegate 및 System.MulticastDelegate)


.NET Framework Class Library는 System.MulticastDelegate 클래스를 정의합니다. 이것은 지난 4월 컬럼에서 다루었던 클래스입니다. 하지만, MulticastDelegate는 실제로 System.Delegate 클래스에서 파생(.NET Framework Class Library에서도 정의)된다는 사실을 알아야 합니다. System.Delegate 클래스는 System.Object에서 파생됩니다.

원래 .NET Framework를 고안할 때, Microsoft 엔지니어들은 두 가지 유형의 위임 즉, 단일 캐스트 및 다중 캐스트를 제공해야 할 필요성을 느꼈습니다. MulticastDelegate 파생 유형은 함께 체인화될 수 있는 위임 개체를 나타내고, 위임의 파생 유형은 함께 체인화될 수 없는 개체를 나타냅니다. System.Delegate 유형은 기본 유형으로 고안되었으며, 이 클래스는 래핑된 메서드를 콜백하기 위해 필요한 모든 기능을 구현했습니다. MulticastDelegate 클래스는 Delegate 클래스에서 파생되었으며 MulticastDelegate 개체의 연결 목록(또는 체인)을 만들 수 있는 기능이 추가되었습니다.

소스 코드를 컴파일할 때 컴파일러는 위임의 서명을 확인하고 두 클래스 중에서 컴파일러가 생성한 위임 유형의 기본 클래스에 더 적합한 클래스를 선택할 것입니다. 참고적으로, void 반환값을 나타내는 서명을 가진 메서드는 System.Delegate에서 파생되고, 비 void 반환값을 가진 메서드는 System.MulticastDelegate에서 파생됩니다. 이 이유는 연결 목록 체인에서 호출된 마지막 메서드에서만 반환값을 얻을 수 있기 때문입니다.

.NET Framework의 베타 테스트 동안 두 개의 서로 다른 기본 유형이 개발자들을 혼란스럽게 만든 것은 분명했습니다. 또한, 이런 식으로 위임을 고안할 경우 개발자들은 임의의 선택을 할 수 밖에 없습니다. 예를 들어, 수많은 경우에 많은 메서드가 무시될 수 있는 반환값을 가집니다. 이러한 메서드는 비 void 반환값을 가지므로, MulticastDelegate 클래스에서 파생될 수 없고 연결 목록에 포함될 수 없습니다.

개발자의 혼란을 줄이기 위해 Microsoft 엔지니어들은 Delegate 및 MulticastDelegate 클래스를 하나의 클래스로 통합하여 모든 위임 개체가 연결 목록 체인에 포함될 수 있도록 하기를 원했습니다. 그러면 모든 컴파일러가 이 하나의 클래스에서 파생되는 위임 클래스를 생성하게 됩니다. 이러한 경우, .NET Framework 팀, 공용 언어 런타임(CLR) 팀, 컴파일러 팀, 위임을 사용하는 이 분야의 외부 개발자들의 혼동과 노력이 상당히 줄어듭니다.

하지만, Delegate 및 MulticastDelegate 클래스를 통합하는 작업은 .NET Framework 개발 단계에서 이후로 미뤄지게 되었으며, Microsoft는 이러한 모든 변경 작업을 수행할 경우 발생할 수 있는 버그 및 테스트 문제에 대해 걱정하지 않을 수 없었습니다. 따라서, .NET Framework의 베타 1에서 이 두 클래스는 통합되지 않았습니다. 이후 버전의.NET Framework에서 하나의 클래스로 통합된다는 사실은 분명합니다.

Microsoft가 .NET Framework Class Library에서 이 두 클래스의 통합을 연기하기로 결정한 가운데, Microsoft는 모든 Microsoft 컴파일러를 수정하여 이제서야 항상 MulticastDelegate 클래스에서 파생된 위임의 유형을 생성하도록 하였습니다. 따라서, 지난 번 컬럼에서 모든 위임의 유형이 MulticastDelegate에서 파생된다는 말은 거짓말이었습니다. 컴파일러를 이런 식으로 변경하였기 때문에 위임 유형의 모든 인스턴스는 콜백 메서드의 반환값에 관계 없이 연결 목록 체인에 포함될 수 있습니다.

그렇다면, 이러한 사실을 왜 이해해야 하는가? 위임으로 점점 더 많은 작업을 할수록.NET Framework SDK 설명서에서 Delegate 및 MulticastDelegate 유형 모두를 분명히 발견하게 될 것입니다. 저는 이러한 두 클래스 사이의 관계를 이해하기를 바랬습니다. 또한, 생성하는 모든 위임 유형이 기본 클래스로 MulticastDelegate를 가지더라도 MulticastDelegate 클래스 대신 Delegate 클래스에 의해 정의된 메서드를 사용하여 유형을 조작해야 할 경우가 있습니다.

예를 들어, Delegate 클래스는 Combine 및 Remove라는 정적 메서드를 가집니다.(이러한 메서드가 어떤 역할을 하는지에 대해서는 나중에 설명을 하겠습니다.) 이러한 두 메서드에 대한 서명은 Delegate 매개 변수를 가져야 함을 나타냅니다. 위임의 유형은 MulticastDelegate에서 파생되고, 이것은 다시 Delegate에서 파생되었으므로 이 위임 유형의 인스턴스는 Combine 및 Remove 메서드에 전달될 수 있습니다.


위임의 동일성 비교

위임의 기본 클래스는 Object의 가상 Equals 메서드를 무효화합니다. MulticastDelegate 유형은 Equals의 Delegate 구현을 상속합니다. Equals의 Delegate 구현은 두 위임 개체를 비교하여 해당 _target 및 _methodPtr 필드가 동일한 개체 및 메서드를 참조하는지 확인합니다. 이러한 두 필드가 일치할 경우 Equals는 True를 반환하고, 그렇지 않을 경우 Equals는 False를 반환합니다. 다음 코드는 이것을 나타냅니다.

// Construct 2 delegate objects that refer to the 
// same target/method
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToConsole);

// Even though fb1 and fb2 refer to two different objects 
// internally, they both refer to the same callback 
// target/method.
Console.WriteLine(fb1.Equals(fb2));   // Displays "True"
또한, Delegate 및 MulticastDelegate 유형은 모두 등호(==) 및 부등호(!=) 연산자에 대한 오버로드를 제공합니다. 그러므로, Equals 메서드를 호출하는 대신 이러한 연산자를 사용할 수 있습니다. 다음 코드는 위의 코드와 동일합니다.
// Construct 2 delegate objects that refer to the same target/method
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToConsole);

// Even though fb1 and fb2 refer to two different objects internally,
// they both refer to the same callback target/method.
Console.WriteLine(fb1 == fb2);   // Displays "True"
다음 절에서 다루어지는 위임의 체인 조작을 시도할 때 동일성에 대한 위임의 비교 방법을 이해하는 것은 중요합니다.


위임 체인

위임은 자체적으로 매우 유용합니다. 하지만, 위임은 위임을 더욱 유용하게 만드는 체인화도 지원합니다. 지난 번 컬럼에서 각 MulticastDelegate 개체는 _prev라는 전용 필드를 가진다고 말했습니다. 이 필드는 다른 MulticastDelegate 개체에 대한 참조를 포함합니다. 즉, MulticastDelegate 유형(또는 MulticastDelegate에서 파생된 모든 유형)의 모든 개체는 다른 MulticastDelegate 파생 개체에 대한 참조를 가집니다. 이 필드는 위임 개체가 연결 목록의 일부가 되도록 합니다.

Delegate 클래스는 위임 개체의 연결 목록 체인을 조작하는 데 사용할 수 있는 3개의 정적 메서드를 정의합니다.
class System.Delegate {
   // Combines the chains represented by head & tail, head is returned
   // (NOTE: head will be the last delegate called)
   public static Delegate Combine(Delegate tail, Delegate head);

   // Creates a chain represented by the array of delegates
   // (NOTE: entry 0 is the head and will be the last delegate called)
   public static Delegate Combine(Delegate[] delegateArray);

   // Removes a delegate matching value’s Target/Method from the chain.
   // The new head is returned and will be the last delegate called
   public static Delegate Remove(Delegate source, Delegate value);
}
새로운 위임 개체를 만들 때 해당 개체의 _prev 필드는 연결 목록에 다른 개체가 없음을 나타내는 null로 설정됩니다. 두 위임을 연결 목록으로 결합하기 위해서는 위임의 정적 Combine 메서드 중 하나를 호출해야 합니다.
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
// The left side of Figure?1 shows what the 
// chain looks like after the previous code executes

App appobj = new App();
Feedback fb3 = new Feedback(appobj.FeedbackToStream);
fbChain = (Feedback) Delegate.Combine(fbChain, fb3);
Figure 1 Delegate Chains
[그림 1] Delegate 체인

그림 1은 모든 코드가 실행된 후 체인이 어떻게 보이는지를 나타냅니다. Delegate 유형은 Delegate 참조의 배열을 취하는 다른 버전의 Combine 메서드를 제공한다는 것을 알 수 있습니다. 이 버전의 Combine을 사용하여 위의 코드를 다음과 같이 다시 작성할 수 있습니다.
Feedback[] fbArray = new Feedback[3];
fbArray[0] = new Feedback(FeedbackToConsole);
fbArray[1] = new Feedback(FeedbackToMsgBox);
App appobj = new App();
fbArray[2] = new Feedback(appobj.FeedbackToStream);

Feedback fbChain = Delegate.Combine(fbArray);
위임이 호출되면 컴파일러가 해당 위임 유형의 Invoke 메서드에 대한 호출을 생성합니다(이전 컬럼에서도 언급). 기억을 돕기 위해 이전 컬럼의 예에서는 다음 코드와 같이 Feedback 위임을 선언했습니다.
public delegate void     
   Feedback(
   Object value, Int32 item, Int32 numItems);
이것으로 인해 컴파일러가 다음과 같이(의사 코드) Invoke 메서드를 포함하는 Feedback 클래스를 생성합니다.
class Feedback : MulticastDelegate {
   public void virtual Invoke(
      Object value, Int32 item, Int32 numItems) {

      // If there are any delegates in the chain that
      // should be called first, call them
      if (_prev != null) _prev.Invoke(value, item, numItems);

      // Call our callback method on the specified target object
     _target.methodPtr(value, item, numItems);
   }
}

보는 바와 같이, 위임 개체를 호출하면 이전의 위임이 먼저 호출됩니다. 이전의 위임이 반환될 때 해당 반환값은 버려집니다. 이전의 위임을 호출한 후 위임은 이것이 래핑하는 콜백 대상/메서드를 호출할 수 있습니다. 그림 2의 코드는 이 프로세스를 나타냅니다.

지금까지는 위임 유형 Feedback이 void 반환값과 함께 정의되는 예를 보여주었습니다. Feedback 위임을 다음과 같이 정의할 경우,
public delegate Int32 Feedback(
   Object value, Int32 item, Int32 numItems);
해당 Invoke 메서드는 내부적으로 다음과 같이(다시 의사 코드) 나타납니다.
class Feedback : MulticastDelegate {
   public Int32 virtual Invoke(
      Object value, Int32 item, Int32 numItems) {

      // If there are any delegates in the chain that
      // should be called first, call them
      if (_prev != null) _prev.Invoke(value, item, numItems);

// Call our callback method on the specified target object 
   return _target.methodPtr(value, item, numItems);
   }
}

위임 체인의 헤드가 호출되면 체인에서 이전 위임을 모두 호출합니다. 이전 위임의 반환값은 버려진다는 것을 유의하십시오. 응용 프로그램 코드는 체인의 헤드에 있는 위임(마지막으로 호출된 콜백 메서드)의 반환값만 받아들입니다.

위임 개체가 만들어지면 변경할 수 없는 것으로 간주됩니다. 즉, 위임 개체는 항상 null로 설정된 _prev 필드를 가지며 이것은 변경되지 않습니다. 위임 개체를 체인에 결합할 경우 Combine은 내부적으로 소스 개체와 동일한 _target 및 _methodPtr 필드를 가지는 새로운 위임 개체를 만듭니다. _prev 필드는 체인의 이전 헤드로 설정됩니다. 새로운 위임 개체의 주소는 Combine에서 반환됩니다(그림 3의 코드 참조).

위임 체인을 만드는 방법을 알았으므로 체인에서 위임을 제거하는 방법을 살펴보겠습니다. 연결 목록에서 위임을 제거하려면 그림 4와 같이 Delegate 유형의 정적 Remove 메서드를 호출해야 합니다. 코드는 먼저 두 위임 개체를 만들어 체인을 만든 다음, Combine 메서드를 호출하여 연결 목록에 이러한 개체를 결합합니다. 그런 다음, Remove 메서드가 호출됩니다. Remove의 첫 번째 매개 변수는 위임 개체 체인의 헤드를 가리키고, 두 번째 매개 변수는 체인에서 제거될 위임 개체를 가리킵니다. 체인에서 위임 개체를 제거하기 위해 새로운 위임 개체를 만들어야 한다는 것이 이상하게 보일 수도 있습니다. 왜 이렇게 해야 하는지에 대해 이해하기 위해서는 약간의 추가 설명이 필요합니다.

Remove에 대한 호출에서 새로운 위임 개체를 만듭니다. 이 위임 개체는 적절하게 초기화된 _target 및 _methodPtr 필드와 null로 설정된 _prev 필드를 가집니다. Remove 메서드는 체인(fbChain에 의해 참조)을 검사하여 체인의 위임 개체 중에서 새로운 위임 개체와 일치하는 것이 있는지 확인합니다. Delegate 클래스에 의해 구현된 무효화되는 Equals 메서드는 _target 및 _methodPtr 필드만 비교하며 _prev 필드는 무시한다는 사실을 유의하십시오.

일치하는 개체가 발견될 경우, Remove 메서드는 이전 위임 개체의 _prev 필드를 수정하여 체인에서 찾은 위임 개체를 제거합니다. Remove는 새로운 체인의 헤더를 반환합니다. 일치하는 개체가 발견되지 않을 경우, Remove는 아무런 작업도 수행하지 않으며(예외가 발생되지 않음) 첫 번째 매개 변수에 대해 전달된 동일한 값을 반환합니다.

Remove에 대한 각 호출은 그림 5의 코드와 같이 체인에서 하나의 객체만 제거합니다.

C# 개발자들의 더 쉬운 작업을 위해 C# 컴파일러는 위임 유형의 인스턴스에 대해 += 및 -= 연산자의 오버로드를 제공합니다. 이들 연산자는 각각 Delegate.Combine 및 Delegate.Remove를 호출합니다. 이러한 연산자를 사용하면 위임 체인을 쉽게 만들 수 있습니다. 그림 6의 C# 코드는 C# 연산자를 사용하여 체인에서 위임 개체를 결합하고 제거하는 코드를 쉽게 만들 수 있다는 것을 보여줍니다.

내부적으로, 컴파일러는 위임에 대한 모든 += 사용을 위임의 Combine 메서드로 해석합니다. 마찬가지로, 위임 개체에 대한 모든 -= 연산자 사용은 Remove 메서드에 대한 호출로 해석됩니다. 실제로, 방금 보여준 코드를 만들고 ILDasm.exe를 사용하여 해당 중간 언어를 볼 수 있습니다. 그러면 C# 컴파일러가 정말로 모든 += 및 -= 연산자를 각각 Delegate 유형의 정적 Combine 및 Remove 메서드에 대한 호출로 바꾸었다는 사실을 확인할 수 있습니다.


Delegate 체인 호출


이제, 위임 개체의 연결 목록 체인을 만들고 해당 체인에서 모든 개체를 호출하는 방법에 대해 알았을 것입니다. 위임 유형의 Invoke 메서드는 이전의 위임(존재하는 경우)을 호출하는 코드를 포함하고 있기 때문에 연결 목록 체인의 모든 항목이 호출됩니다. 이것은 분명히 매우 간단한 알고리즘입니다. 이 로직은 만날 수 있는 많은 상황에서 충분하겠지만 많은 제한 사항도 가지고 있습니다.

예를 들어, 콜백 메서드의 반환값은 마지막의 것만 제외하고 모두 버려집니다. 이 간단한 알고리즘을 사용할 경우 호출되는 모든 콜백 메서드에 대한 반환값을 얻을 방법이 없습니다. 하지만, 이 알고리즘은 더욱 많은 제한 사항을 가지고 있습니다. 예를 들어, 호출된 위임 중 하나가 예외를 발생시키거나 매우 오랜 시간 동안 막고 있을 경우 어떻게 해야 하는가? 알고리즘은 체인의 각 위임을 순차적으로 호출하기 때문에 위임 개체 중 하나에 문제가 발생할 경우 체인의 다른 모든 위임이 호출되지 못합니다. 분명히 이것은 믿을 만한 알고리즘은 되지 못합니다.

이 로직이 충분하지 않은 상황의 경우 MulticastDelegate 클래스는 필요에 만족하는 알고리즘을 사용하여 체인의 각 위임을 명시적으로 호출하는 데 사용할 수 있는 인스턴스 메서드인 GetInvocationList를 제공합니다.
public class MulticastDelegate {
   // Creates a delegate array; each item is a clone from the chain
   // (NOTE: entry 0 is the tail, which would normally be called first)
   public virtual Delegate[] GetInvocationList();
}

GetInvocationList 메서드는 위임 체인에 대한 참조로 동작하고 위임 개체에 대한 참조의 배열을 반환합니다. 내부적으로, GetInvocationList는 지정된 체인을 확인하여 체인의 각 개체에 대한 복제본을 만들고 이러한 복제본을 배열에 추가합니다. 각 복제본은 null로 설정된 _prev 필드를 가지므로 각 개체는 격리되고 다른 개체의 체인을 참조하지 않습니다.

이제, 배열에서 각 개체를 명시적으로 호출하는 알고리즘을 쉽게 작성할 수 있을 것입니다. 그림 7의 코드는 이러한 알고리즘을 나타냅니다.

Jeff에게 질문 또는 의견을 보내려면 dot-net@microsoft.com으로 보내십시오. ?

Jeffrey RichterProgramming Applications for Microsoft Windows(Microsoft Press, 1999)의 저자이며 소프트웨어 교육, 디버깅, 컨설팅 전문 회사인 Wintellect(http://www.wintellect.com/) 공동 설립자입니다. .NET 및 Win32의 프로그래밍/디자인이 전공이며, 현재 Microsoft .NET Framework 프로그래밍 책을 집필 중이고 .NET 기술 세미나를 진행하고 있습니다.



? 최종수정일: 2001년 6월 15일

Top of Page Top of Page


Microsoft