이벤트를 호출하는 경우 다양한 문제가 발생할 수 있다.
이벤트를 호출할 때 이벤트 핸들러가 결합되어 있지 않은 경우에 대한 처리는 C# 6.0에서 추가된 null 조건 연산자를 사용하여 간단하게 처리할 수 있다.
이전에는 이벤트 핸들러가 결합되어 있는지 확인하기 위해 직접 null 체크를 하고 호출하는 방식이 많이 사용되었는 이 방식은 두 단계로 이루어져 있기 때문에 경쟁 조건이 발생할 가능성이 있습니다.
코드로 알아보기
01 Null 체크 안하는 경우
public class EventSource
{
private EventHandler<int> Updated;
public void RaiseUpdates()
{
counter++;
Updated(this,counter);
}
private int counter;
}
이 코드에서 RaiseUpdates 메서드에서 Updated 이벤트를 호출할 때, 먼저 counter 값을 증가시킨 후에 Updated 이벤트를 호출한다. 하지만 이 코드는 Updated 이벤트에 대한 null 체크를 수행하지 않기 때문에 만약 이벤트에 이벤트 핸들러가 결합되어 있지 않은 상태에서 RaiseUpdates가 호출되면 NullReferenceException이 발생할 수 있다.
02 Null 체크 추가
public void RaiseUpdates()
{
counter++;
if(Updated != null)
Updated(this,counter);
}
이 부분에서는 Updated 이벤트에 대한 null 체크를 추가하고 이벤트 핸들러가 결합되어 있는지 확인한다. 결합되어 있다면 이벤트를 호출하도록 한다.
이벤트를 발생시키는 코드를 수행하기 직전에 다른 쓰레드가 이벤트 핸들러의 등록을 취소했다고 가정하면 원래 쓰레드로 돌아와 이벤트를 발생시키면 이벤트 핸들러는 null값을 가지게 되어서 NullReferenceException예외가 발생한다.
03 멀티스레드 환경에서의 안전한 코드
public void RaiseUpdates()
{
counter++;
var handler = Updated;
if(handler != null)
handler(this,counter);
}
Updated 이벤트를 handler라는 지역 변수에 할당한 후에 이 변수를 통해 이벤트 핸들러를 호출한다. 이렇게 하면 다른 스레드가 이벤트 핸들러를 제거하더라도 handler에 할당된 시점의 이벤트 핸들러 목록을 사용하여 호출하므로, NullReferenceException 예외가 발생하지 않고 안전하게 이벤트를 발생시킬 수 있다.
하지만 코드가 너무 복잡하다는 단점이 있다. 그래서 null조건 연산자를 사용하자!
null조건 연산자
조건 연산자(?.)을 기준으로 연산자의 왼쪽을 평가하여 이 값이 null이 아닌 경우에만 오른쪽의 표현식을 실행하고 null인경우 아무 작업도 수행하지 않는다.
public void RaisedUpdates()
{
counter++;
Updated?.Invoke(this.counter);
}
결론
무조건 null 조건 연산자를 사용하자. 기존 방식보다 더욱 단순하고 명확하므로 사용하지 않을 이유가 없다.
본 게시글은 Effective C#을 읽고 정리하였습니다.
'책 > Effective C#' 카테고리의 다른 글
Effective C# - Item10 베이스 클래스가 업그레이드된 경우에만 new 한정자를 사용하라 (0) | 2023.11.22 |
---|---|
Effective C# - Item9 박싱과 언박싱을 최소화하 (0) | 2023.11.22 |
Effective C# - Item7 델리게이트를 이용하여 콜백을 표현하라 (0) | 2023.11.22 |
Effective C# - Item6 nameof() 연산자를 적극 활용하라 (0) | 2023.11.19 |
Effective C# - Item5 문화권별로 다른 문자열을 생성하려면 FormattableString을 사용하라 (0) | 2023.11.16 |
댓글