1. 소개
이런 상황을 한번 상상해봐. 코드에서 고양이 데이터를 저장하고 있어. 그리고 고양이의 나이뿐만 아니라 "데이터 없음", "나이 모름" 같은 상태도 저장하고 싶어. 0을 저장할 수도 있겠지만, 0은 실제로 유효한 나이잖아.
int age = 0; // 고양이 나이. 근데 0이 뭐야? 아기 고양이? 아니면 "모름"?
문제는 int는 정수만 받을 수 있고, "데이터 없음" 같은 건 정수에선 불가능하다는 거야. 만약 나이가 문자열이면 null을 넣을 수 있겠지. 근데 숫자에선 그게 안 돼:
int age = null; // 오류! int 타입 변수에 null을 넣을 수 없어
이건 값 타입 (struct류, 예: int, double, DateTime, bool)은 항상 뭔가 값을 가지고 있기 때문이야. 얘네는 "비어 있음" 상태가 없어 (참조 타입은 null이 그냥 객체 없음이지만).
실생활 예시:
이 제한을 우회하려고 개발자들이 "특별한" 값을 만들기도 해: 예를 들어 -1이나 int.MaxValue를 "값 없음"으로 쓰는 거지. 근데 이건 별로야. 헷갈리기도 쉽고, 진짜 값이랑 구분도 힘들어.
2. Nullable 타입: "null"이 될 수 있는 타입
아이디어
만약 일반 숫자(그리고 모든 값 타입)가 값뿐만 아니라 null도 가질 수 있다면 어떨까?
C#에서는 이걸 Nullable<T>라는 특별한 래퍼 클래스로 구현했어. 이 클래스는 T 타입 값이나 아무것도 없는 상태(null) 둘 중 하나를 가질 수 있어.
문법
int? age = null; // age 변수는 숫자도 되고 null도 될 수 있어
C# 컴파일러는 이런 코드를 보면 사실상 이렇게 인식해:
Nullable<int> age = new Nullable<int>(); // age 변수는 값이 없음
혹시 값을 명시적으로 넣고 싶으면:
Nullable<int> age = new Nullable<int>(42); // age에 42가 들어감
이렇게 물음표를 붙이면 어떤 값 타입이든 nullable 버전으로 바뀌는 거야:
- int? (이건 Nullable<int>랑 똑같아)
- double?
- DateTime?
- 그리고 다른 struct들도 다 가능
가장 많이 쓰는 struct용 Nullable 래퍼
| 일반 타입 | Nullable 타입 | 예시 코드 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
래퍼 클래스를 써서 길게 쓸 수도 있어:
Nullable<int> pages = null;
근데 int?가 훨씬 짧고, 그래서 더 멋져.
3. Nullable 타입 사용법
값 할당과 체크
null 할당은 참조 타입이랑 똑같이 돼:
선언과 할당
int? temperature = null;
temperature = 25;
null 체크
nullable 타입에 진짜 값이 있는지 어떻게 알 수 있을까?
if (temperature != null)
{
Console.WriteLine($"온도: {temperature}");
}
else
{
Console.WriteLine("온도 데이터 없음");
}
Nullable 타입의 속성: .HasValue 와 .Value
nullable 타입엔 중요한 속성 두 개가 있어:
- .HasValue — 변수에 뭔가 들어있으면(null 아니면) true를 반환해.
- .Value — 값이 있으면 그 값을 주고, 없으면 예외가 나와.
int? temperature = null;
if (temperature.HasValue) //예외 안 남!
{
Console.WriteLine($"온도: {temperature.Value}°C");
}
else
{
Console.WriteLine("온도 모름");
}
이 코드는 잘 돌아가: temperature는 null이랑 같지 않고, 내부에 null을 담고 있어. Nullable 타입의 HasValue 속성은 언제든 쓸 수 있어. 근데 .Value를 값 없이 꺼내려 하면 InvalidOperationException 예외가 나니까, 꼭 값 있는지 먼저 체크하자!
간단 출력 문법
많은 경우 그냥 nullable 타입 변수를 써도 C#이 알아서 처리해줘:
int? hours = null;
Console.WriteLine(hours); // 아무것도 안 나옴(그냥 빈칸)
hours = 10;
Console.WriteLine(hours); // 10이 나옴
4. 연산과 nullable 타입: 산술, 비교, 변환
연산: 어떻게 될까?
피연산자 중 하나라도 nullable이면 결과도 nullable이야.
그리고 피연산자 중 하나가 null이면 결과도 null이 돼.
int? a = 5;
int? b = null;
int? sum = a + b; // sum == null
int? c = 10;
int? d = 15;
int? total = c + d; // total == 25
비교 연산
nullable 타입을 일반 숫자랑 비교할 수 있어:
int? score = null;
if (score > 0) Console.WriteLine("좋은 결과야!");
else Console.WriteLine("결과 없음"); // 이쪽이 실행됨
score가 null이면 score > 0 조건은 false야.
명시적/암시적 변환
- 일반 타입에서 nullable로: 자동 변환.
- nullable에서 일반 타입으로: null이 아니어야만 가능(아니면 예외).
int? x = 3;
int y = x.Value; // x가 null 아니면 Ok
// 암시적 변환
int? z = y;
5. Nullable과 메서드: 파라미터와 반환값
nullable 값 반환하기
메서드가 항상 결과를 반환할 수 없는 경우, 반환 타입을 nullable로 쓰면 돼:
// 메서드가 로그인으로 유저를 찾아서 나이를 반환, 못 찾으면 null
int? FindUserAge(string login)
{
// ... 여기서 찾는 로직
return null; // 못 찾았을 때
}
Nullable 파라미터
nullable 값을 파라미터로 받을 수도 있어. "있을 수도, 없을 수도 있음"을 명확히 보여주는 거지.
void PrintTemperature(int? temp)
{
if (temp == null)
Console.WriteLine("온도 모름");
else
Console.WriteLine($"온도: {temp} 도");
}
GO TO FULL VERSION