C# 6.0에서 소개되었던 널 조건 연산자(Null-conditional operators)란 기능이다. 그전에는 아무런 객체도 참조하지 않을 때 NullReferenceException 예외를 처리해야 했기 때문에 검사 코드를 계속해서 써 내려가야 했지만, 이제는 연산자 하나로 깔끔하게 해결할 수 있게 되었다.


기존에는 아래와 같이 null인지 검사하는 코드가 반복적으로 등장하는 경우가 많았다. 개발자 입장에서는 핵심 코드가 뒤로 밀리거나, 매번 단순한 검사 코드를 계속해서 적어주어야 하는 귀찮음이 있었다.

public static string Truncate(string value, int length)
{
    string result = value;
    if (value != null) // 설명을 위해 빈 문자열 검사는 건너뜀
    {
        result = value.Substring(0, Math.Min(value.Length, length));
    }
    return result;
}

그런데 정말 감사하게도 C# 6.0 부터 이를 연산자 하나로 간략화할 수 있게 되었다. 상당히 단순하고 명확한 녀석이다.

연산자

 설명

사용 예시 

?.

멤버에 접근하기 전 체크

 customers?.

?[]

인덱스 작업을 수행하기 전 체크 

 customers?[0]

두 연산자 모두 왼쪽 피연산자의 값을 검사 후, 이 값이 null이 아닌 경우에만 연산자 오른쪽의 표현식을 실행한다. 만약 왼쪽 피연산자의 값이 null이라면 연산자 오른쪽의 표현식을 실행하지 않고 null을 반환한다. 


그러면 이제 어떻게 위의 예제 코드를 방금 소개한 연산자를 사용하여 바꿀 수 있는지 살펴보자.

public static string Truncate(string value, int length)
{          
  return value?.Substring(0, Math.Min(value.Length, length));
}

여기서 Substring() 메서드를 호출하기 위해 멤버 value에 접근하기 이전에, value가 null인지 아닌지 먼저 검사한다. 만약 null이라면, 연산자 뒤의 내용은 실행하지 않고 다음 문장으로 넘어간다. 이때 Truncate() 메서드는 null을 반환하게 될 것이다. null이 아닐 경우에는 연산자 다음의 내용을 실행한다. 이를 그림으로 다시 확인해보도록 하자.

한번만 예제를 더 보도록 하자.

int? length = customers?.Length; // customers가 null일 경우 null
Customer first = customers?[0];  // customers가 null일 경우 null
int? count = customers?[0]?.Orders?.Count();  // customers, customers[0], Orders 중 하나라도 null일 경우 null

여기서 마지막 문장만 보면 총 3개의 널 조건 연산자가 사용되었고, 'customers 검사 -> customers[0] 검사 -> customers.Orders 검사'와 같이 진행됨을 볼 수 있다. 'customers가 null', 'customers[0]가 null', 'customers[0].Orders가 null'인 경우에만 Nullable 형식 변수 count에 null이 들어가게 된다.