[C#] null 조건부, 병합 연산자 : ?., ??, ??=

[C#] null 조건부, 병합 연산자 : ?., ??, ??=

안녕하세요. 이번 글에서는 C#의 null 처리 연산자인 ?., ??, ??= 에 대해 알아보겠습니다.

참조 형식 변수가 null일 때 멤버에 접근하면 NullReferenceException이 발생합니다. 이를 피하려고 if (x != null) 검사를 반복하면 코드가 길어지는데, 세 연산자를 사용하면 같은 처리를 한 줄로 작성할 수 있습니다.

한눈에 보기

연산자이름동작
?.null 조건부 연산자왼쪽이 null이면 멤버 접근을 건너뛰고 null을 반환
?[ ]null 조건부 인덱서왼쪽이 null이면 인덱스 접근을 건너뛰고 null을 반환
??null 병합 연산자왼쪽이 null이면 오른쪽 값을 사용
??=null 병합 할당 연산자 (C# 8.0)왼쪽이 null일 때만 오른쪽 값을 할당

?. 와 ??

?.의 결과는 null이 될 수 있으므로, 값 형식 멤버에 사용하면 결과 타입은 int?처럼 Nullable이 됩니다. 이때 ??로 기본값을 지정하면 편리합니다.

using System;

string name = null;

int? length = name?.Length;
string display = name ?? "(이름 없음)";

Console.WriteLine(length.HasValue);
Console.WriteLine(display);

name = "홍길동";
Console.WriteLine(name?.Length);
Console.WriteLine(name ?? "(이름 없음)");

출력은 순서대로 False, (이름 없음), 3, 홍길동 입니다. name이 null일 때는 Length에 접근하지 않고 null을 돌려주며, 값이 있으면 일반 접근과 동일하게 동작합니다.

?[ ] 와 연결해서 사용하기

?.는 연결해서 쓸 수 있고, 중간 어느 한 곳이 null이면 전체 결과가 null이 됩니다. 컬렉션 자체가 null일 수 있을 때는 ?[ ]를 사용합니다.

using System;
using System.Collections.Generic;

List<string> names = null;

string first = names?[0];
int count = names?.Count ?? 0;

Console.WriteLine(first == null);
Console.WriteLine(count);

names가 null이어도 예외가 발생하지 않으며 출력은 True, 0 입니다.

??= : 비어 있을 때만 할당

??=는 왼쪽이 null일 때만 오른쪽 값을 할당합니다. 지연 초기화(lazy initialization)에 자주 쓰입니다.

using System;
using System.Collections.Generic;

List<int> list = null;

list ??= new List<int>();
list.Add(1);

var same = list;
list ??= new List<int>();

Console.WriteLine(list.Count);
Console.WriteLine(ReferenceEquals(list, same));

두 번째 ??=에서는 list가 이미 null이 아니므로 새 객체가 만들어지지 않습니다. 출력은 1, True 입니다.

주의할 점

  • ?.는 null 검사만 건너뛸 뿐, 오른쪽 메서드의 부수효과까지 막아 주는 것은 아닙니다. 왼쪽이 null이면 오른쪽 전체가 실행되지 않습니다.
  • 값 형식(int, bool 등)에는 null이 없으므로 ??는 Nullable(int?)이나 참조 형식에 사용합니다.
  • null이 올 수 없는 값에 ?.를 남발하면 오히려 오류를 숨길 수 있어, 실제로 null이 가능한 곳에만 사용합니다.

정리

  • ?. 는 null이면 접근을 건너뛰고 null을 반환합니다.
  • ?? 는 null일 때 대체값을 제공합니다.
  • ??= 는 null일 때만 값을 할당합니다.

이상으로 null 처리 연산자에 대해 간단히 알아보았습니다.

참고