1. Nullable 값 형식 (?)
C#에서 참조 형식은
null 값을 가질 수 있지만, 일반적인 값 형식(int, bool, struct 등)은 기본적으로
null을 허용하지 않습니다. 예를 들어 다음 코드는 컴파일 오류를 발생시킵니다:
int number = null; // 오류!
이 문제를 해결하기 위해 C#은
nullable 값 형식을 제공하며, 이는 타입 뒤에 물음표(
?)를 붙여 표현합니다:
int? nullableInt = null;
DateTime? optionalDate = DateTime.Now;
여기서
int?는 사실
System.Nullable<int>의 축약형입니다. 컴파일러는 내부적으로 모든
T?를
Nullable<T>로 변환합니다. 이 구조체는 값과 함께
HasValue 및
Value 속성을 통해 null 여부를 안전하게 확인할 수 있게 해줍니다.
2. 조건부 삼항 연산자 (?:)
삼항 연산자는 간단한 조건 분기를 위한 식이며, 전통적인
if-else 문의 단축형입니다:
string result = condition ? "성공" : "실패";
이 식은
condition이 참이면 왼쪽 피연산인 "성공"을, 거짓이면 오른쪽 피연산인 "실패"를 반환합니다. 이 연산자는 반환값이 필요할 때 매우 유용하며, 할당이나 메서드 인수 전달 시 자주 사용됩니다.
3. null 병합 연산자 (??)
?? 연산자는 좌측 피연산자가
null이 아닌 경우 그 값을 사용하고,
null인 경우 우측의 대체 값을 반환합니다:
string name = inputName ?? "기본 이름";
int count = list?.Count() ?? 0;
또한 이 연산자는 오른쪽 결합성을 가지므로, 여러 개를 연결할 수 있습니다:
string finalValue = value1 ?? value2 ?? value3 ?? "기본값";
이는
value1이 null이 아니면 그것을 사용하고, 그렇지 않으면
value2를 평가하는 식으로 진행됩니다.
4. null 조건부 멤버 접근 (?.)
null 조건부 연산자
?.는 객체가
null인지 확인하지 않고도 안전하게 멤버에 접근할 수 있게 해줍니다. 기존에는 다음과 같이 중첩된 if 문이 필요했습니다:
int? x = null;
if (points != null)
{
var first = points.FirstOrDefault();
if (first != null)
x = first.X;
}
C# 6.0부터는 이를 한 줄로 줄일 수 있습니다:
int? x = points?.FirstOrDefault()?.X;
만약
points가
null이거나 첫 번째 요소가 없으면 전체 식은
null을 반환하며, 런타임 예외가 발생하지 않습니다. 중요한 점은 반환 형식이 원래 값 형식이라도
?. 사용 시
nullable 형식이 된다는 것입니다:
Point p = new Point(1, 2);
Console.WriteLine(p.X.GetType() == typeof(int)); // True
Console.WriteLine((p?.X).GetType() == typeof(int?)); // True
5. null 조건부 인덱스 연산자 (?[ ])
비슷한 맥락에서, 컬렉션이나 배열이
null일 가능성이 있을 때 인덱스 접근도 안전하게 수행할 수 있습니다:
int? orderCount = customers?[0]?.Orders?.Count();
여기서
customers가
null이거나 인덱스 0의 요소가 존재하지 않으면, 전체 식은
null을 반환하며
NullReferenceException이 발생하지 않습니다. 특히 복잡한 객체 그래프를 탐색할 때 코드를 간결하고 안전하게 만들어 줍니다.
정리
C#의 이러한 연산자들은 모두
코드의 안정성과 가독성을 높이기 위한 언어 기능입니다:
T?: 값 형식에 null 허용
?:: 간단한 조건 선택
??: null일 때 기본값 제공
?.: null일 때 멤버 접근 방지
?[]: null일 때 인덱스 접근 방지
이 모든 것은 일관된 패턴으로 구성되어 있으며, 컴파일러가 적절히 최적화하여 안전한 IL 코드로 변환합니다.