1. null 병합 연산자 (??)
nullable 타입에 대해 잘 알기 위해서는 간결하고 안전한 코드를 쓸 줄 알아야 해. C#에는 이걸 도와주는 두 개의 엄청 유용한 연산자가 있어: null 병합 연산자(??)랑 null-조건부 연산자(?.).
?? 연산자는 null일 수 있는 변수에 “기본값”을 지정할 수 있게 해줘.
예를 들어, 사용자 이름이 null일 수도 있다고 해보자. 만약 진짜 null이면 "손님"을 보여주고 싶을 때 이렇게 할 수 있어:
string userName = null;
string displayName = userName != null ? userName : "손님";
?? 연산자를 쓰면 더 간단하게 쓸 수 있어:
string userName = null;
string displayName = userName ?? "손님";
작동 방식:
- 왼쪽 표현식이 null이 아니면 그 값을 반환해.
- 왼쪽이 null이면 오른쪽 값을 써.
다른 예시도 있어:
int? age = null;
int displayAge = age ?? -1; // -1 — 기본값
Console.WriteLine(displayAge); // -1 출력
string input = null;
string name = input ?? "손님";
Console.WriteLine($"안녕, {name}!"); // 안녕, 손님!
연산자 체이닝
??를 체인처럼 여러 번 쓸 수도 있어:
string result = str1 ?? str2 ?? "기본값";
str1이 null이 아니면 그걸 쓰고, 아니면 str2를, 그것도 null이면 "기본값" 문자열을 써.
2. null-조건부 연산자 (?.)
또 자주 나오는 상황이, 객체가 null이 아닐 때만 메서드를 호출하거나 프로퍼티에 접근하고 싶을 때야. 예를 들어:
User user = null;
string displayName = user != null ? user.Name : null;
?. 연산자를 쓰면 더 간단해져:
User user = null;
string displayName = user?.Name;
user가 null이면 에러가 안 나고 그냥 null을 반환해.
예시:
User user = null;
// ?. 없이 — 에러 발생
// Console.WriteLine(user.Name); // NullReferenceException
Console.WriteLine(user?.Name); // 안전 — 빈 문자열이나 아무것도 출력 안 됨
Console.WriteLine(user?.GetProfileInfo()); // 이것도 마찬가지
User[] users = null;
int? count = users?.Length; // users == null이면 null
연산자 체이닝
이렇게 "체인"도 만들 수 있어:
string domain = company?.Director?.Email?.Split('@')?[1];
경로 중간에 뭐라도 null이면, 예외 안 나고 그냥 null을 반환해.
??와 조합
?.랑 ??는 같이 쓰면 진짜 좋아:
string display = user?.Name ?? "알 수 없음";
user나 user.Name이 null이면 "알 수 없음"이 출력돼.
3. ! 이건 뭐고 언제 써?
null 경고 억제 연산자
최신 C# 버전(NRT 켜져 있을 때)에서는, null일 수 있는 변수에 접근하면 컴파일러가 친절하게 경고를 해줘. 근데 네가 진짜로 "이건 무조건 null 아님!"이라고 확신할 때는 경고 억제 연산자 !를 쓸 수 있어.
string? possibleNull = GetUserNameMaybeNull();
Console.WriteLine(possibleNull.Length); // Warning: null일 수 있음
Console.WriteLine(possibleNull!.Length); // 컴파일러는 조용, 근데 진짜 null이면 예외 터짐
중요: !는 에러를 막아주지 않아 — 그냥 컴파일러한테 "믿어줘"라고 말하는 거야. 만약 진짜 null이면 NullReferenceException이 터져.
언제 쓰면 안 될까?
!를 아무 생각 없이 쓰지 마. 좋은 스타일은 최대한 안 쓰는 거야. null이 불가능하거나, 명확하게 처리되도록 코드를 짜는 게 더 좋아.
4. default — 타입의 "기본값" 얻기
가끔은 그냥 변수를 초기 상태로 "리셋"하고 싶을 때가 있어. 직접 0, false, null을 쓰기 귀찮을 때 말이지.
이럴 때 default 키워드를 쓰면 돼.
int a = default; // a == 0
bool flag = default; // flag == false
string s = default; // s == null
double? d = default; // d == null
네 미니 앱에서 이름이나 나이를 리셋할 때도 쓸 수 있어:
userName = default; // null
userAge = default; // null (userAge가 int?일 때)
5. 차이점과 뉘앙스: ?, !, default
경험 많은 개발자도 ?, !, default가 한데 나오면 헷갈릴 때가 있어. 누가 누군지 정리해보자.
? — null을 허용한다는 뜻
- 값 타입: int? x — 이제 x는 null이 될 수 있어.
- 참조 타입: string? s — 이 변수는 null일 수 있다고 명시(NRT 모드에서).
! — null이 절대 아니라고 주장
- user!.Name — user는 절대 null이 아니라고 컴파일러에게 약속하는 거야.
- Nullable Reference Types 모드에서만 동작해.
default — "기본값"을 달라는 뜻
- int x = default; — x는 0이 돼
- string s = default; — s는 null이 돼
비교:
| 문법 | 이게 뭐야? | 어디서 써? | null이랑 무슨 관계? |
|---|---|---|---|
|
nullable 값 타입 | 어디서나 | null을 넣을 수 있음 |
|
nullable 참조 타입 (NRT) | NRT 모드에서 | null을 넣을 수 있음 |
|
null-forgiving operator | NRT 모드에서 | 경고 억제 |
|
기본값 | 어디서나 | 참조 타입은 null |
6. ?, !, default 쓸 때 흔한 실수
실수 1: !가 null을 "치유"한다고 착각함.
사실 !는 그냥 컴파일러가 뭐라 안 하게 하는 거야. 값이 진짜 null이면 프로그램은 NullReferenceException으로 터져.
실수 2: .Value 쓰기 전에 .HasValue 체크를 까먹음.
특히 int?, bool? 같은 nullable 타입에서. 체크 안 하면 InvalidOperationException 예외가 나올 수 있어.
실수 3: "특별한" 0 의미가 필요한데 default를 써버림.
예를 들어 0이나 false가 실제 값일 수도 있는데, 이걸 "비어있음" 신호로 오해하면 논리적 오류가 생길 수 있어.
실수 4: NRT 모드에서 참조 타입에 ?를 안 쓰거나, 너무 남발함.
누군가는 ?를 까먹어서 컴파일러 경고가 잔뜩 나오고, 또 누군가는 ?를 남발해서 null이 절대 안 나오는 곳에도 붙여. 둘 다 코드 가독성과 신뢰성을 떨어뜨려.
GO TO FULL VERSION