포스트

Nullable과 null 병합 연산자, null 조건 연산자

null이란, “값이 없는” 혹은 “어떤 객체도 참조하지 않은” 참조형 변수를 말합니다.
C#에서 null을 가질 수 있는 타입은 참조 타입입니다. 값 타입null일 수 없습니다.
다시 말해, C#에서 null은 “어떤 객체도 참조하지 않은 참조형 변수의 디폴트 값”이라고 할 수 있습니다.

값 형식에서의 디폴트 값(초기화 하지 않을 때 가지는 값)은 “0”입니다.
정수형은 “0”, bool 타입은 “false(0)”, char 타입은 ‘\0’을 가지지만, 이건 값이 없는 상태라고 할 수는 없습니다.

Nullable<T> 타입은 이런 값 형식(예: int, double, bool 등)도 null 값을 가질 수 있게 해주는 기능입니다.

Nullable 타입 정의와 사용법

Nullable 타입 정의

nullable 타입 혹은 Nullable<T> 타입은 제네릭 구조체(Generic Struct) 입니다.

Nullable<T>값 타입(Value Type)인 ‘T’가 ‘null’ 값을 가질 수 있도록 확장해주는 구조체입니다.

1
2
3
4
5
6
public partial struct Nullable<T> where T : struct
{
    private readonly bool hasValue; // Do not rename (binary serialization)
    internal T value; // Do not rename (binary serialization) or make readonly (can be mutated in ToString, etc.)
    //...
}

실제 Nullable<T>hasValuevalue 두 개의 필드를 가지고 있습니다.

  • bool hasValue: 값이 있는지 여부를 나타내는 불리언 필드입니다. 이것으로 null 값을 가질 수 있는 것처럼 동작합니다.
  • T value: 실제 값을 저장하는 필드입니다. 만약 hasValuefalse인 경우 value에 접근하면 InvalidOperationException이 발생합니다.

사용법

사용 방법은 다른 제네릭을 쓰듯이 T대신 원하는 자료형을 사용하고, 실제 null 변수를 다루듯이 다루면 됩니다.

1
2
3
4
5
6
Nullable<int> i = null;

if (i == null)
{
    Console.WriteLine("int i 가 null 입니다.");  // int i 가 null 이므로 이 문자 출력
}

또한, Nullable<T> 표기의 축약형으로, 값 형식 뒤에 ? 기호를 함께 붙이는 것으로 Nullable 타입을 정의할 수 있습니다.

예를 들어, int?Nullable<int> 의 축약형 입니다.

1
2
3
int? nullableInt = null;
double? nullableDouble = null;
bool? nullableBool = null;

Nullable 타입 반환

GetValueOrDefault()

Nullable에 할당 된 값을 Nullable이 아닌 값 타입에 넣으려고 하면 “암시적으로 ‘int?’ 형식을 ‘int’ 형식으로 변환할 수 없습니다.”라는 말을 합니다.
그렇다고 value에 그냥 접근해서 가져오는 것도 위험할 수 있습니다.(null일 때, value 필드에 접근하면 InvalidOperationException을 발생시킵니다.)

이때, 사용할 수 있는 메서드가 GetValueOrDefault()입니다.

1
2
3
4
5
6
int? nullableInt = 42;

// int num1 = nullableInt; // 암시적으로 'int?' 형식을 'int' 형식으로 변환할 수 없습니다.
int num2 = nullableInt.GetValueOrDefault(); // null이면 0을 반환

Console.WriteLine(num2); // 출력: 42

GetValueOrDefault() 메서드는 값이 있으면 ‘value’를 반환하고, 값이 없으면 해당 값 타입(‘T’)의 기본값을 반환합니다.

?? (Null 병합 연산자)

또 다른 방법으로는 null 병합 연산자라고도 불리는 연산자(??)를 사용하는 것입니다.

해당 연산자는 피연산자1 ?? 피연산자2의 형태로 사용하며, 피연산자1이 null이 아니라면 피연산자1 값 그대로, ‘null’이면 피연산자2의 값을 반환하는 연산자입니다.

1
2
string? nullableStr = null;
Console.WriteLine($"str : {nullableStr ?? "Null"}"); // str : Null

Nullable 타입 비교

Nullable 타입을 비교할 때는 일반 값 타입과 동일하게 비교할 수 있습니다.
Nullable 타입이 null일 경우 다른 Nullable 타입이 null인지 확인하거나, null이 아닌 값과 같은지 비교할 수 있습니다.

1
2
3
4
5
6
int? a = 5;
int? b = null;

Console.WriteLine(a == b); // 출력: False
Console.WriteLine(a == 5); // 출력: True
Console.WriteLine(b == null); // 출력: True

Null 조건 연산자 (?.)

다만, 다음과 같이 null의 멤버에 접근하려고 하면, NullReferenceException이 발생합니다.

1
2
3
4
5
6
string? nullableStr = null;

if (nullableStr.Length > 0) // NullReferenceException
{
    Console.WriteLine("nullableStr.Length > 0");
}

보통은 이 문제를 해결하기 위해 null의 가능성이 있는 참조형 앞에 null 검사를 붙여줍니다.

1
if (nullableStr != null && nullableStr.Length > 0)

하지만, C# 6.0부터는 null 조건 연산자 (?.)를 사용할 수 있습니다.

null 조건 연산자는 멤버 접근 연산자(.)나 인덱스 연산자([])와 같이 사용되어 피연산자의 값이 null이면 null을 리턴합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
string? nullableStr = null;
int? length = nullableStr?.Length;  // nullableStr이 null이면 null을 반환
Console.WriteLine($"length : {length}");    // length :
Console.WriteLine($"length?? : {length ?? -1}"); // length?? : -1

if(nullableStr?.Length > 0) // false
{
    Console.WriteLine("nullableStr.Length > 0");
}
else
{
    Console.WriteLine("nullableStr.Length <= 0");
}

정리

  • Nullable<T> 타입은 값 타입에 null을 할당할 수 있게 해주는 제네릭 구조체입니다.
    • T? 형태의 nullable 축약형으로도 정의할 수 있습니다.
    • value가 null이면 InvalidOperationException이 발생합니다.
  • GetValueOrDefault() 메서드로 nullable의 값 혹은 기본 값을 반환합니다.
  • ??(Null 병합 연산자)로 피연산자가 null 일 때, 다른 형태로 반환할 수 있습니다.
  • ?.(Null 조건 연산자)로 피연산자의 멤버로 접근하기 전에 null 체크를 할 수 있습니다.

참고

C#의 값 타입과 참조 타입

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.