Quick Reference
nullable reference type(NRT)는 string과 string?로 null 의도를 적고 compiler가 흐름을 분석해 경고하는 기능입니다. runtime type이나 null 동작을 바꾸지 않으므로, public boundary와 외부 입력에는 별도의 runtime 검증이 필요합니다.
#nullable enable
string required = "Mina";
string? optional = GetNickname();
if (optional is not null)
{
Console.WriteLine(optional.Length); // 이 block에서는 not-null
}<Nullable>enable</Nullable>또는#nullable enable로 nullable context를 켭니다.string은 null이 아니어야 한다는 contract,string?는 null 가능성을 caller에게 알리는 contract입니다.!는 compiler 경고만 억제합니다. null을 검사하거나 바꾸지 않습니다.?.와?[]는 null receiver만 처리합니다. index 범위 오류나 비즈니스 규칙 오류를 처리하지 않습니다.
Annotation과 흐름 분석
compiler는 assignment와 null check 뒤의 null-state를 추적합니다. ?는 실제 CLR type을 새로 만들지 않으며, string과 string? 모두 runtime에서는 System.String입니다.
string? ReadName(bool found) => found ? "Mina" : null;
string? name = ReadName(found: false);
string display = name ?? "anonymous";
if (name is { Length: > 0 })
{
Console.WriteLine(name.ToUpperInvariant());
}API가 일반적인 flow analysis로 표현하기 어려운 계약을 가질 때는 System.Diagnostics.CodeAnalysis attribute를 사용합니다.
using System.Diagnostics.CodeAnalysis;
static bool TryReadName(string? input, [NotNullWhen(true)] out string? name)
{
name = input?.Trim();
return !string.IsNullOrEmpty(name);
}
if (TryReadName(" Mina ", out string? parsed))
{
Console.WriteLine(parsed.Length); // attribute 때문에 not-null로 분석
}Runtime 경계와 연산자
NRT는 compile-time analysis입니다. JSON, reflection, dynamic, nullable-oblivious library 같은 외부 경로는 null을 전달할 수 있으므로, public API가 null을 허용하지 않으면 runtime guard를 둡니다.
public static string Normalize(string name)
{
ArgumentNullException.ThrowIfNull(name);
return name.Trim().ToUpperInvariant();
}value!는 "여기서는 null이 아님을 개발자가 보증한다"는 warning suppression입니다. 실제 null이면 다음 dereference에서 같은 NullReferenceException이 납니다. compiler가 모르는 validation 뒤처럼 근거가 짧고 국소적인 곳에만 씁니다.
string?[] names = ["Mina"];
int? length = names?[1]?.Length; // names가 null이면 null, index 1은 여전히 범위 오류
// cache ??= Create(); // null일 때 대입하지만 thread-safe lazy initialization은 아님null defaulting과 ??=의 정확한 연산 규칙은 null 병합 연산자에서 다룹니다.
API를 설계하는 방법
필수 input은 non-nullable parameter로 선언하고 boundary에서 guard합니다. 선택 input·없을 수 있는 return은 ?로 표기합니다. 실패 이유가 여러 개인 탐색 API는 null 하나로 모두 표현하기보다 Try... 결과, error type, result object 중 호출자가 처리할 수 있는 계약을 고릅니다.
public static bool TryFindUser(
IReadOnlyDictionary<int, string> users,
int id,
[NotNullWhen(true)] out string? name)
=> users.TryGetValue(id, out name);레거시 code를 enable할 때는 warning을 !로 일괄 억제하지 않습니다. project 또는 파일 범위를 정해 nullable context를 켜고, public surface와 data construction부터 contract를 맞춘 뒤 내부 흐름을 좁혀 갑니다.
자주 틀리는 부분
nullable warning이 없다는 사실은 runtime input이 절대 null이 아니라는 증명이 아닙니다. annotation은 코드 작성자끼리 공유하는 contract이고, runtime guard는 외부 경계를 지키는 별도 책임입니다.
string?은 빈 문자열, 형식 오류, array index 범위 오류까지 나타내지 않습니다. null 가능성과 다른 실패 조건을 한 값으로 뭉개지 마세요.
참고 링크
2 sources