in 소프트웨어 개발

왜 C# 이벤트를 빈 델리게이트로 초기화 할까?

 

새벽에 후배가 물어봤다. C# 오픈소스 라이브러리를 하나 뜯어보고 있는데 이벤트를 쓰기전에 왜 일단 인스턴스를 생성부터 해놓을까요?

KakaoTalk_Photo_2016-06-30-04-04-35

글게, 왜 굳이 빈 델리게이트를 넣었을까?

걍 후반에 인보크 할때 null예외 안나게 하려는거 아님? 하고 답장했는데, 잘 확신이 안서 검색했다.

C# 이벤트를 빈 델리게이트 초기화 라는 키워드로 검색하니 답이 나왔다.

http://stackoverflow.com/questions/11609236/c-sharp-set-empty-delegate-for-event

 

이 짧은 줄 하나 덕분에 앞으로 스레드 문제 걱정 하나 준다는 사실에 감탄했다.

 

질문:

public event EventHandler MyButtonClick = delegate { };

위 코드는 구독자가 존재하는지 체크할 필요가 없게 해준다.

public virtual void OnMyButtonClick(EventHandler e)
        {
            this.MyButtonClick(this, e);
        }

위 코드를 아래 코드 대신에 쓰게 해준다.

  public virtual void OnMyButtonClick(EventHandler e)
            { 
                if (MyButtonClick!=null)
                   this.MyButtonClick(this, e);
            }

그런데 이게 좋은 생각인가? 이러한 구성의 장점은 단지 이벤트 구독자가 존해는지 체크할 필요가 없다는 것 뿐인데, 굳이 이렇게 할 필요가 있나?


 

스레드 기반에서 안전하지 못하기 때문이란다.

빈 델리게이트라도 일단 넣어주면, 구독자 혹시 없는지 체크할 필요가 없다. 하지만 그 반대 경우에는 이렇게 null 체크를 넣는다.

하지만 위 코드에서 만약 MyButtonClick의 null체크가 통과한 상태에서 마지막 구독자가 다른 스레드에 의해 제거되어 버리고, 그 다음에 인보크 된다면 null예외가 난다.

public virtual void OnMyButtonClick(EventArgs e)
{ 
    var handler = MyButtonClick;
    if (handler != null)
    {
        handler(this, e);
    }
}

물론 이런식으로 지역변수에 카피해서 다른 스레드에 의해 MyButtonClick의 레퍼가 날아가는 타이밍과 상관없이 에러를 막을 순 있겠다.

(이벤트와 델리게이트는 레퍼런스지만 동시에 immutable 타입이니, 원본이 날아간다고 카피본이 날아가는게 아니다)

 

 

하지만 이러면 번거롭다.

암튼 그러니 맞는 말이다. 이것의 장점은 단지 null체크를 안할 수 있는 것 뿐이다. 하지만 나쁜 거래는 아니다. 어찌됬든 간단한 한줄로 찾기 어려운 실수를 미연에 방지한다면 좋은 얘기 아닌가.

 

요렇게 해도 된단다.

public static void SafeInvoke(this EventHandler handler, object sender,
                              EventArgs e)
{
    if (handler != null)
    {
        handler(sender, e);
    }       
}

Then change your calling code to:

public virtual void OnMyButtonClick(EventArgs e)
{
    MyButtonClick.SafeInvoke(this, e);
}

위 코드를 설명하면 뭐 this 를 파라미터로 넣어 클래스의 메소드를 확장하는 기능, C#3.0 이후의 ‘매직’은 사실 컴파일러 상에선 static 메소드로 처리되는 거니까,MyButtonClick 자기 자신이 복사되어 들어가는 건데, 이벤트는 immutable 타입이니까 결국 하드카피되서 들어갔다 보면됨. 그러니 다른 스레드에 의해  MyButtonClick이 직후 날아가도 문제 없는 것.