!
! (null-forgiving) 연산자 (C# 레퍼런스)
단항 후위 ! 연산자는 null-forgiving, 혹은 null-suppression 연산자예요. nullable 주석 컨텍스트가 활성화된 상태에서 이 연산자를 쓰면, 앞에 나오는 식에서 발생하는 모든 nullable 경고를 끄게 됩니다. 여기서 헷갈리기 쉬운 게 하나 있는데요, 단항 전위 ! 연산자는 논리 부정 연산자예요. 이 둘은 완전히 다른 연산자니까 기호만 보고 헷갈리지 않도록 조심하세요.
null-forgiving 연산자는 런타임에는 아무 영향도 없어요. 오직 컴파일러의 정적 흐름 분석에만 영향을 줘서, 그 식의 null 상태를 바꿔줄 뿐이에요. 실제 실행에서 x!는 밑에 있는 식 x의 결과를 그대로 평가해요. 즉 '컴파일러야, 여기는 null 아니라고 내가 보증할게'라고 알려주는 장치지, 실제로 null 검사를 해주는 건 아니라는 뜻이에요.
nullable 참조 타입 기능에 대해 더 자세히 알고 싶다면 Nullable reference types 문서를 확인해 주세요.
본문
예시
null-forgiving 연산자가 유용하게 쓰이는 대표적인 경우 중 하나는 인자 검증(argument validation) 로직을 테스트하는 거예요. 예를 들어 이런 클래스가 있다고 생각해 볼게요.
#nullable enable
public class Person
{
public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
public string Name { get; }
}
생성자의 검증 로직을 테스트한다면, MSTest 테스트 프레임워크로 다음과 같은 테스트를 만들 수 있어요.
[TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void NullNameShouldThrowTest()
{
var person = new Person(null!);
}
null-forgiving 연산자가 없다면, 위 코드에 대해 컴파일러는 이런 경고를 냈을 거예요. Warning CS8625: Cannot convert null literal to non-nullable reference type. null-forgiving 연산자를 쓰면 '여기 null을 넘기는 건 의도한 동작이고, 경고를 내지 않아도 된다'는 걸 컴파일러에게 알려줘요.
이 연산자는 또 하나의 상황에서 유용한데요, 바로 '분명히 null일 리 없는 식'인데 컴파일러는 그걸 알아채지 못할 때예요. 아래 예시를 보면, IsValid 메서드가 true를 반환한다면 그 인자는 null이 아니고, 그래서 안전하게 역참조(dereference)할 수 있어요.
public static void Main()
{
Person? p = Find("John");
if (IsValid(p))
{
Console.WriteLine($"Found {p!.Name}");
}
}
public static bool IsValid(Person? person)
=> person is not null && person.Name is not null;
여기서 null-forgiving 연산자가 없다면, p.Name 코드에 대해 컴파일러는 이런 경고를 냈을 거예요. Warning CS8602: Dereference of a possibly null reference.
만약 IsValid 메서드 쪽을 수정할 수 있는 상황이라면, NotNullWhen 특성을 써서 'IsValid가 true를 반환할 때는 그 인자가 null일 수 없다'는 걸 컴파일러에게 알려줄 수도 있어요.
public static void Main()
{
Person? p = Find("John");
if (IsValid(p))
{
Console.WriteLine($"Found {p.Name}");
}
}
public static bool IsValid([NotNullWhen(true)] Person? person)
=> person is not null && person.Name is not null;
이 예시에서는 null-forgiving 연산자를 쓸 필요가 없어요. if 문 안에서는 p가 null일 수 없다는 걸 컴파일러가 스스로 알아낼 만큼 충분한 정보를 갖게 되거든요. 변수의 null 상태에 대한 추가 정보를 제공해 주는 특성들을 더 알고 싶다면, Upgrade APIs with attributes to define null expectations 문서를 참고해 주세요.
C# 언어 사양
더 자세한 내용은 C# 언어 사양의 The null-forgiving operator 절을 참고해 주세요.
더 알아보기
- Remove unnecessary suppression operator (style rule IDE0080)
- C# operators and expressions
- Tutorial: Design with nullable reference types