2016년 2월 25일 목요일

C# 프로그래밍 가이드 중 일부 발췌 - 대리자(delegates), 열거형, 이벤트

개인적인 copy & paste 정리입니다. 글을 보시기 보다는 아래 링크에 방문하세요.

https://msdn.microsoft.com/ko-kr/library/67ef8sbd.aspx


대리자(delegates)

  • 대리자는 C++의 함수 포인터와 유사하지만 형식이 안전합니다.
  • 대리자를 통해 메서드를 매개 변수로 전달할 수 있습니다.
  • 대리자를 사용하여 콜백 메서드를 정의할 수 있습니다.
  • 여러 대리자를 연결할 수 있습니다. 예를 들어 단일 이벤트에 대해 여러 메서드를 호출할 수 있습니다.
  • 메서드와 대리자 형식이 정확히 일치할 필요는 없습니다. 자세한 내용은 대리자의 가변성 사용(C# 및 Visual Basic)을 참조하십시오.
  • C# 버전 2.0에는 별도로 정의된 메서드 대신 코드 블록을 매개 변수로 전달할 수 있도록 하는 무명 메서드라는 개념이 도입되었습니다. C# 3.0에는 인라인 코드 블록을 더 간단하게 작성할 수 있는 람다 식이 도입되었습니다. 특정 컨텍스트에서는 무명 메서드와 람다 식 모두 대리자 형식으로 컴파일됩니다. 이 두 기능을 익명 함수라고 합니다. 람다 식에 대한 자세한 내용은 익명 함수(C# 프로그래밍 가이드)를 참조하십시오.
public delegate void Del(string message);
// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}
// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}
MethodWithCallback(1, 2, handler);
그러면 콘솔에 다음 출력이 표시됩니다.
The number is: 3

인스턴스 메서드를 래핑하기 위한 대리자를 생성할 때 해당 대리자는 인스턴스와 메서드를 모두 참조합니다. 대리자는 래핑 대상 메서드 이외의 인스턴스 형식은 알 수 없으므로 해당 개체에 대리자 서명과 일치하는 메서드가 있으면 모든 개체 형식을 참조할 수 있습니다. 정적 메서드를 래핑하기 위해 생성하는 대리자는 메서드만 참조합니다. 다음의 선언을 살펴보세요.

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
이 시점에서 allMethodsDelegate의 호출 목록에는 Method1, Method2, DelegateMethod의 3개 메서드가 포함되어 있습니다. 원래 대리자 3개(d1, d2, d3)는 그대로 유지됩니다. allMethodsDelegate를 호출하면 3개 메서드가 모두 순서대로 호출됩니다. 대리자가 참조 매개 변수를 사용하는 경우 참조는 각 3개 메서드에 순서대로 전달되며 메서드 하나의 변경 내용은 다음 메서드에도 표시됩니다. 메서드 중 하나라도 메서드 내에서 catch되지 않은 예외를 throw하면 해당 예외가 대리자의 호출자에게 해당 예외가 전달되며 호출 목록의 후속 메서드는 호출되지 않습니다.
대리자 형식은 System.Delegate에서 파생되므로 해당 클래스로 정의되는 메서드와 속성을 대리자에서 호출할 수 있습니다. 예를 들어 대리자의 호출 목록에 있는 메서드 수를 확인하려는 경우 다음과 같이 코드를 작성하면 됩니다.
int invocationCount = d1.GetInvocationList().GetLength(0);

컴파일 타임에 할당된 서로 다른 형식의 두 대리자를 비교하면 컴파일 오류가 발생합니다. 대리자 인스턴스가 정적System.Delegate 형식이면 비교는 허용되지만 런타임에서 false가 반환됩니다. 예를 들면 다음과 같습니다.
delegate void Delegate1();
delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f 
    // is not the same as that of d.
    System.Console.WriteLine(d == f);
}


인터페이스 대신 대리자를 사용해야 하는 경우

다음과 같은 경우에 대리자를 사용합니다.
  • 이벤트 디자인 패턴을 사용하는 경우
  • 정적 메서드를 캡슐화해야 하는 경우
  • 메서드를 구현하는 개체에 대한 다른 속성, 메서드 또는 인터페이스에 호출자가 액세스할 필요가 없는 경우
  • 쉽게 작성하려는 경우(Easy composition is desired.)
  • 메서드에 대한 여러 구현이 클래스에 필요한 경우
다음과 같은 경우에 인터페이스를 사용합니다.
  • 호출할 수 있는 관련 메서드의 그룹이 있는 경우
  • 클래스에 메서드의 구현이 하나만 필요한 경우
  • 인터페이스를 사용하는 클래스에서 이 인터페이스를 다른 인터페이스나 클래스 형식에 캐스팅하려는 경우
  • 비교 메서드의 경우와 같이 구현하려는 메서드가 클래스의 형식이나 동일성 여부와 관련되어 있는 경우

=> delegates의 경우는 해당 method나 function을 호출하여 결과를 알려주거나 trigger시키는 역할이니 caller와 callee가 상관없을 경우에 사용. Interfaces의 경우는 상속 관점에서 뭔지는 모르겠지만, 자식들에서 구현한 것을 호출하거나 interfae를 이용해서 객체들을 구분한다든지, caller와 callee간 어느 정도 관계가 있으면 사용

https://msdn.microsoft.com/ko-kr/library/0yw3tz5k.aspx

무명 메서드를 만드는 것은 본질적으로 코드 블록을 대리자 매개 변수로 전달하기 위한 방법입니다.두 가지 예를 들겠습니다.

  // Create a handler for a click event.
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };

  // Create a delegate.
delegate void Del(int x);

// Instantiate the delegate using an anonymous method.
Del d = delegate(int k) { /* ... */ };

무명 메서드의 매개 변수 범위는 무명 메서드 블록입니다.
무명 메서드 블록 안에서 블록 밖을 대상으로 goto, break 또는 continue와 같은 점프 문을 사용하면 오류가 발생합니다. 무명 메서드 블록 밖에서 블록 안을 대상으로 goto, break 또는 continue와 같은 점프 문을 사용해도 오류가 발생합니다.
지역 변수 및 매개 변수의 범위에 무명 메서드 선언이 포함되는 경우 이러한 변수를 무명 메서드의 외부 변수라고 합니다.예를 들어, 다음 코드 단편에서 n은 외부 변수입니다.
int n = 0;
Del d = delegate() { System.Console.WriteLine("Copy #:{0}", ++n); };
외부 변수에 n 라고 캡처 대리자를 만들 때.무명 메서드를 참조 하는 대리자가 가비지 수집의 대상이 될 때까지 지역 변수와 달리 캡처된 변수의 수명은 확장 합니다.
무명 메서드에서는 외부 범위의 ref 또는 out 매개 변수에 액세스할 수 없습니다.
무명 메서드 블록에서는 안전한 코드에만 액세스할 수 있습니다.
무명 메서드는 is 연산자의 왼쪽에 사용할 수 없습니다.


C# 버전 별 대리자 선언, 인스턴스화 및 사용

https://msdn.microsoft.com/ko-kr/library/ms173176.aspx

C# 1.0 이상에서는 다음 예제와 같이 대리자를 선언할 수 있습니다.

// Declare a delegate.
delegate void Del(string str);

// Declare a method with the same signature as the delegate.
static void Notify(string name)
{
    Console.WriteLine("Notification received for: {0}", name);
}

// Create an instance of the delegate.
Del del1 = new Del(Notify);

C# 2.0에서는 비슷한 방법을 통해 다음 예제에서와 같이 이전 예제의 선언을 작성할 수 있습니다.
// C# 2.0 provides a simpler way to declare an instance of Del.
Del del2 = Notify;
C# 2.0 이상에서는 다음 예제에서와 같이 무명 메서드를 사용하여 대리자를 선언하고 초기화할 수도 있습니다.
// Instantiate Del by using an anonymous method.
Del del3 = delegate(string name)
    { Console.WriteLine("Notification received for: {0}", name); };
C# 3.0 이상에서는 다음 예제에서와 같이 람다 식을 사용하여 대리자를 선언하고 인스턴스화할 수도 있습니다.
// Instantiate Del by using a lambda expression.
Del del4 = name =>  { Console.WriteLine("Notification received for: {0}", name); };

열거형

https://msdn.microsoft.com/ko-kr/library/cc138362.aspx
모든 열거형은 System.Enum 형식의 인스턴스입니다. System.Enum 에서 새 클래스를 파생시킬 수 없지만 해당 메서드를 사용하면 열거형 인스턴스에 대한 정보를 검색하고 값을 조작할 수 있습니다.
string s = Enum.GetName(typeof(Days), 4);
Console.WriteLine(s);

Console.WriteLine("The values of the Days Enum are:");
foreach (int i in Enum.GetValues(typeof(Days)))
    Console.WriteLine(i);

Console.WriteLine("The names of the Days Enum are:");
foreach (string str in Enum.GetNames(typeof(Days)))
    Console.WriteLine(str);


이벤트

프로그래밍 방식으로 이벤트를 구독하려면

  1. 시그니처가 해당 이벤트의 대리자 시그니처와 일치하는 이벤트 처리기 메서드를 정의합니다. 예를 들어, 이벤트가 EventHandler 대리자 형식을 기반으로 하는 경우 다음 코드는 메서드 스텁을 나타냅니다.
      void HandleCustomEvent(object sender, CustomEventArgs a)
      {
         // Do something useful here.
      }
    
  2. 이벤트에 이벤트 처리기를 연결하려면 더하기 할당 연산자(+=)를 사용합니다. 다음 예제에서는 publisher 개체에 RaiseCustomEvent 이벤트가 있다고 가정합니다. 구독자 클래스에서 해당 이벤트를 구독하려면 게시자 클래스에 대한 참조가 필요합니다.
    publisher.RaiseCustomEvent += HandleCustomEvent;
    
    위의 구문은 C# 2.0의 새로운 구문입니다. 이 구문에 해당하는 C# 1.0 구문에서는 new 키워드를 사용하여 캡슐화 대리자를 명시적으로 만들어야 합니다.
    publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);
    
    람다 식을 사용하여 이벤트 처리기를 추가할 수도 있습니다.
    public Form1()
    {
        InitializeComponent();
        // Use a lambda expression to define an event handler.
        this.Click += (s,e) => { MessageBox.Show(
           ((MouseEventArgs)e).Location.ToString());};
    }
    

무명 메서드를 사용하여 이벤트를 구독하려면

  • 나중에 이벤트에 대한 등록을 취소할 필요가 없는 경우 더하기 할당 연산자(+=)를 사용하여 이벤트에 무명 메서드를 연결합니다. 다음 예제에서는 publisher 개체에 RaiseCustomEvent 이벤트가 있으며 몇 가지 특수 이벤트 정보를 전달하도록 CustomEventArgs 클래스도 정의되었다고 가정합니다. 구독자 클래스에서 해당 이벤트를 구독하려면 publisher에 대한 참조가 필요합니다.
      publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
      {
        string s = o.ToString() + " " + e.ToString();
        Console.WriteLine(s);
      };
    
    익명 함수를 사용하여 이벤트를 구독한 경우 이벤트를 쉽게 구독 취소할 수 없다는 점에 주의하십시오. 이 경우에 이벤트를 구독 취소하려면 이벤트를 구독하는 코드로 돌아가서 대리자 변수에 무명 메서드를 저장하고 이벤트에 대리자를 추가해야 합니다. 일반적으로 코드의 뒷부분에서 이벤트에 대한 구독을 취소해야 한다면 이벤트를 구독하는 데 익명 함수를 사용하지 않는 것이 좋습니다. 익명 함수에 대한 자세한 내용은 익명 함수(C# 프로그래밍 가이드)를 참조하십시오.
빼기 할당 연산자(-=)를 사용하여 이벤트를 구독 취소합니다.
  publisher.RaiseCustomEvent -= HandleCustomEvent;
모든 구독자가 이벤트를 구독 취소한 경우 게시자 클래스에 있는 해당 이벤트 인스턴스는 null로 설정됩니다.

예외 및 예외 처리

다를바 없음..

프로그래머는 다음과 같은 조건 중 하나 이상이 true일 때 예외를 throw해야 합니다.
  • 메서드의 정의된 기능을 완료할 수 없는 경우. 
  • 개체 상태를 기준으로 개체에 대한 적절하지 않은 호출을 수행한 경우.
    • 읽기 전용 파일에 쓰려고 하는 경우를 예로 들 수 있습니다. 개체 상태 때문에 작업을 수행할 수 없는 경우 InvalidOperationException의 인스턴스나 이 클래스의 파생을 기반으로 한 개체가 throw됩니다. 다음은 InvalidOperationException 개체를 throw하는 메서드의 예제입니다.  
  • 메서드에 대한 인수로 인해 예외가 발생하는 경우.

다음은 예외를 throw할 때 주의해야 할 사항입니다.
  • 예외는 일반적인 실행의 일부로 프로그램의 흐름을 변경하는 데 사용하면 안 됩니다. 예외는 오류를 보고하고 처리하는 용도로만 사용해야 합니다.
  • 예외는 반환 값이나 매개 변수로 반환하지 말고 반드시 throw해야 합니다.
  • 사용자가 직접 작성한 코드에서 System.Exception, System.SystemException, System.NullReferenceException 또는System.IndexOutOfRangeException을 고의적으로 throw하면 안 됩니다.
  • 릴리스 모드가 아니라 디버그 모드에서 throw될 수 있는 예외는 만들면 안 됩니다. 개발 단계에서 런타임 오류를 식별하려면 Debug Assert를 대신 사용하십시오.










댓글 없음:

댓글 쓰기