널(Null) 다루기

  • 18 minutes to read

프로그래밍 언어에서 널(Null, NULL, null, nil)은 아무것도 없는 상태를 나타냅니다. 이번에는 널 관련 기능을 정리하는 시간을 갖도록 하겠습니다.

> // null: 아무 것도 없음을 의미하는 리터럴, 개체가 아무 것도 참조하지 않음을 null 참조라 함

null 값

이미 우리는 null을 많이 사용해 왔습니다. 다음 참고 그림의 3번째처럼 null은 참조 형식이면서 아무 것도 참조하지 않음을 나타냅니다. 화살표(포인터)가 아무것도 가리키지 않아 값 자체가 없음을 의미합니다.

그림: null 값

null 값

널 값은 지금까지 사용해 본 경험으로 다음과 같이 추가로 정리를 해보겠습니다. 한 번 정도 읽고 넘어가세요.

  • 아무런 값이 없음
  • 참조형 변수에 아무런 값을 설정하지 않음
  • 알려지지 않은 값으로 아무 의미가 없거나 모르는 값 또는 값이 없음을 의미
  • 변수가 아무런 값도 가리키고 있지 않음
  • 변수가 이름만 만들어지고 아무런 참조를 하지 않음
  • 개체가 만들어지고 아무런 값도 참조하지 않음을 나타냄
  • 영어 단어로 undefined의 의미
  • 빈값(Empty, "")과는 다름

널 값 사용하기

이번에는 null을 사용해보겠습니다. 이미 사용해 본 예제 형태이므로 int, float, double은 값형(Value Type)이고 string, object는 참조형(Reference Type)인 것을 구분하는 관점으로 살펴보세요.

코드: NullDemo.cs

// null: 아무런 가치가 없음. 참조형 변수에 아무런 값을 설정하지 않음.
> int i = 0;              // 값형(Value Type)
> string s = null;        // 참조형(Reference Type)
> s = "안녕하세요.";
> string empty = "";      // 빈값(Empty)은 null과는 다름
> 
> i
0
> s
"안녕하세요."
> empty
""

널 가능 형식: Nullable<T> 형식

기본 제공 형식을 널이 가능한 형식으로 변경하려면 Nullable<T> 제네릭 형식을 사용하면 됩니다.

boolNullable<bool>의 차이점은 다음과 같습니다.

  • bool 형식은 truefalse를 갖습니다.
  • Nullable<bool> 형식은 true, false, null을 갖습니다.

Nullable<T> 형식을 줄여서 표현하는 방법은 데이터 형식 뒤에 ?(물음표) 기호를 붙이는 것입니다. 예를 들어 bool?, int? 형식으로 널 가능 형식을 만들 수 있습니다.

Nullable<T> 형식의 주요 멤버

Nullable<T> 형식이 제공하는 주요 멤버는 다음과 같습니다.

  • HasValue 속성: 값이 있으면 true, null 값이면 false를 반환
  • Value 속성: 값 반환
  • GetValueOrDefault: 값 또는 기본 값 반환

Nullable<T> 제네릭 구조체를 사용하여 널 가능 형식 변수 만들기

Nullable<T> 제네릭 구조체를 사용하면 널 가능 형식의 변수를 만들 수 있습니다. 다음 코드를 간단히 본 후 넘어갑니다.

코드: NullableOf.cs

> Nullable<bool> bln = null;
> bln.HasValue
false
> bln = true;
> bln.HasValue
true

널 가능 형식 사용하기

이번에는 널 가능 형식Nullable 형식을 사용해보겠습니다.

코드: NullableTypeDemo.cs

> // Nullable 형식: null(값이 없음을 의미)이 할당될 수 있는 형식
> // 참조 형식: null 할당 가능
> string s = null;
> s
null
> 
> // 값 형식: null 할당 불가능 -> 에러
> int i = null;
(1,9): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type
> 
> int? i = null;
> i
null
> 
> double? d = null;
> d
null
> 
> // System.`Nullable<T>` 제네릭 클래스: int?, double? 사용을 권장함
> Nullable<int> ii = null;
> int? ii = null;
> Nullable<double> dd = null;
> double? dd = null;

널(null) 값 다루기 관련 연산자 소개

?? 연산자(널 병합 연산자)

2개의 물음표(??: 더블 퀘스천 마크)로 이루어진 연산자인 널 병합 연산자(Null Coalescing Operator)는 왼쪽 항이 null이 아니면 해당 값을 반환하고 그렇지 않으면 오른쪽 값을 반환합니다. 즉, 피연산자가 null이 아닐 경우 왼쪽 피연산자를 반환하고 null일 경우 오른쪽 피연산자를 반환합니다.

널 병합 연산자는 처음 학습할 때에는 조금 어렵습니다. 다음에 나오는 예제를 타이핑 레벨로 실행해본 후 넘어가고 나중에 다시 살펴봐도 좋습니다.

널 병합 연산자를 사용해보겠습니다.

코드: NullCoalescingNote.cs

> // ?? 연산자(널 병합 연산자(Null Coalescing Operator))
> string nullValue = null;
> string message = "";
> 
> //[1] if 구문으로 null 값 비교
> nullValue = null;
> if (nullValue == null)
. {
.     message = "[1] null이면 새로운 값으로 초기화합니다.";
. }
> message
"[1] null이면 새로운 값으로 초기화합니다."
> 
> //[2] ?? 연산자로 null 값 비교
> nullValue = null;
> message = nullValue ?? "[2] null이면 새로운 값으로 초기화합니다.";
> message
"[2] null이면 새로운 값으로 초기화합니다."
> 
> nullValue = "Hello";
> message = nullValue ?? "[3] Nothing";
> message
"Hello"

[1]번 코드처럼 일반적으로 null 값 비교는 if 문을 사용합니다. if 문을 사용하여 null 값을 비교하는 코드를 [2]번 코드처럼 ?? 연산자를 사용하여 표현할 수 있습니다. ?? 연산자는 이처럼 기존에 if 문으로 잘 표현해 오던 코드를 새로운 형태로 좀 더 간결하게 표현할 때 사용됩니다. 이처럼 널 병합 연산자인 ?? 연산자를 사용하면 null 값이 자료에 대해서 특정 기본값으로 초기화할 때 유용하게 사용할 수 있습니다.

널 병합 연산자로 문자열 변수의 NULL 값 확인하기

문자열 변수에 대한 널 값을 확인하여 기본 값으로 설정하는 방법을 널 병합 연산자로 표현해보겠습니다.

C# Interactive에 다음 코드를 입력한 뒤 실행해보세요.

코드: NullCoalescing.cs

> var result = "";
> var message = "";
> 
> message = null;
> result = message ?? "기본값";
> result // 기본값
"기본값"
> 
> message = "있는값";
> result = message ?? "기본값";
> result // 있는값
"있는값"

널 병합 연산자를 사용하면 특정 변수의 값이 null이면 새로운 "기본값"으로 초기화하고 null이 아닌 값으로 이미 초기화되어 있는 변수는 해당 값을 그대로 사용하게 됩니다.

널 가능 형식에 널 병합 연산자 사용하기

이번에는 Null 가능 형식에 대한 널 병합 연산자를 사용해보겠습니다.

C# Interactive에 다음 코드를 입력한 뒤 실행해보세요.

코드: NullCoalescingOperator.cs

> int? value = null; // null 가능 형식에 null로 초기화
> int defaultValue = value ?? -1; // value가 null이면 -1 대입
> defaultValue
-1

위 코드에서 value 변수에는 null이 입력되었습니다. value ?? -1; 코드에 의해서 value 값이 null이면 null 대신에 -1defaultValue에 할당해 줍니다. 이러한 코드를 사용함으로써 null 때문에 발생하는 에러를 줄일 수 있습니다.

널 병합 연산자와 default 키워드

이번에는 ?? 연산자와 default 키워드를 함께 사용해보겠습니다.

C# Interactive에 다음 코드를 입력한 뒤 실행해보세요.

코드: NullableTypeCheck.cs

> int? x = null;
> int y = x ?? 100; // x가 null이면 100으로 초기화
> int z = x ?? default(int); // 정수형의 기본값인 0으로 초기화
> int z = x ?? default; // 정수형의 기본값인 0으로 초기화
> $"y: {y}, z: {z}"
"y: 100, z: 0"

특정 식의 결과에 null 대신 해당 형식의 기본 값을 저장하고자할 때에는 default(T) 코드와 함께 사용 가능합니다. default(int) 구문은 다음과 같이 default로 줄여 표현해도 됩니다.

> int? x = null;
> int z = x ?? default;
> z
0

널 병합 연산자와 널 가능 형식을 함께 사용하기

널 가능 형식과 널 병합 연산자를 함께 사용하면 다음과 같은 코드도 가능합니다. 간단히 살펴본 후 넘어갑니다.

코드: NullableTypes.cs

> bool? unknown = null;
> if (unknown ?? true)
.     Console.WriteLine("출력됨");
출력됨
> unknown = false;
> if (!unknown ?? false)
.     Console.WriteLine("출력됨");
출력됨

널 조건부 연산자(Null Conditional)

널 조건부 연산자라 불리는 ?. 연산자는 엘비스 연산자라고도 불리며 널 가능 형식 뒤에 붙어서 해당 값이 null인지 테스트할 때 사용됩니다.

이 연산자도 널 병합 연산자와 마찬가지로 처음에는 어려울 수 있으니 코들 작성 후 실행 정도만 해보고 넘어가도 됩니다. 단, 현업 코드에서는 누구나 다 자연스럽게 사용하는 연산자들입니다.

코드: QuestionMarkDot.cs

> // 널 조건부 연산자(Null Conditional)
> double? d = null;
> d
null
> d?.ToString()
null
> 
d가 널이면 `null`을 반환합니다.

> double? d = 1.0;
> d?.ToString()
"1"
> d?.ToString("#.00")
"1.00"
> 
d가 널이 아니면 ToString() 메서드를 실행합니다. 

널 조건부 연산자 사용하기

널 체크 연산자인 널 조건부 연산자널 가능 형식에 사용되어 코드를 줄여서 표현할 수 있습니다.

코드: NullConditional.cs

> // 널 조건부 연산자(Null Conditional Operator): ?. 
> int? len;
> string message;
> 
> message = null;
> len = message?.Length; // null
> len
null
> 
> message = "안녕";
> len = message?.Length; // 2
> len
2

문자열 변수 message의 값이 null이면 ?. 연산자 실행시 null 값을 반환하고 그렇지 않으면 ?. 뒤에 오는 속성 또는 메서드를 실행합니다.

?. 연산자는 ?[] 형태로 배열 또는 아직 배우지 않은 인덱서에도 사용이 됩니다.

널 조건부 연산자와 컬렉션 클래스

이번에는 컬렉션과 널 조건부 연산자를 함께 사용해보겠습니다.

코드: NullConditionalWithCollection.cs

> // ?. 연산자: 컬렉션이 null이면 null, 그렇지 않으면 뒤에 오는 속성 값 반환
> // ?. 연산자는 엘비스의 머리 모양과 비슷하다고 하여 Elvis 연산자라고도 함
> List<string> list = null;
> int? numberOfList;
> 
> //[1] 리스트가 null이면 null 반환
> numberOfList = list?.Count; // null
> numberOfList
null
> 
> list = new List<string>();
> list.Add("안녕하세요."); list.Add("반갑습니다.");
> 
> //[2] 리스트가 `null`이 아니므로 Count 속성의 값인 2 반환
> numberOfList = list?.Count; // 2
> numberOfList
2

제네릭 컬렉션의 값이 null이면 ?. 연산자는 null을 반환하고 그렇지 않으면 ?. 뒤에 있는 컬렉션의 카운트를 나타내는 Count 속성의 값을 반환합니다.

널 조건부 연산자와 널 병합 연산자 함께 사용하기

?. 연산자와 ?? 연산자를 함께 사용하는 예제도 살펴보겠습니다.

다음 코드를 작성 후 실행해보세요.

코드: NullCoalescingWithCollection.cs

> // ?? 연산자: 컬렉션이 `null`이 아니면 해당 값 반환, null이면 뒤에 지정한 값 반환
> // ?. 연산자: 컬렉션이 null이면 null, 그렇지 않으면 뒤에 오는 속성 값 반환
> int num;
> List<string> list;
> 
> //[1] 컬렉션 리스트가 null이면 Count를 읽을 수 없기에 0으로 초기화
> list = null;
> num = list?.Count ?? 0; // null이면 0 반환, 오른쪽 값 사용
> num
0
> 
> //[2] 컬렉션 리스트가 `null`이 아니면 Count 속성의 값을 사용 
> list = new List<string>(); list.Add("또 만나요.");
> num = list?.Count ?? 0; // `null`이 아니기 때문에 왼쪽 값 사용
> num
1

?. 연산자의 결괏값이 null이면 null 대신에 ?? 연산자를 사용하여 새로운 값으로 초기화할 수 있습니다. 2개의 널 관련 연산자가 함께 사용되는 list?.Count ?? 0 형태의 코드를 앞으로 자주 보게 될 것인 조금 복잡해 보여도 그 사용법을 확실히 익혀두면 좋습니다.

null 허용 연산자

C# 8.0부터 도입된 널 참조 형식(Nullable Reference Types) 기능과 함께 사용되는 !. 연산자는 컴파일러에게 해당 변수가 null이 아님을 확신하도록 명시하는 기능을 합니다. 이는 **Null 상태 분석(Nullability Analysis)**을 수행하는 컴파일러의 경고를 무시할 때 사용됩니다.

null 허용 연산자의 원어는 null-forgiving 연산자입니다. 다만, 명확한 한글화된 표현은 Microsoft Learn의 C# 참조 문서에 다음과 같이 null 허용 연산자로 표기가 되어 있어 박용준 강사도 이걸로 표기합니다.

null 허용 연산자

코드: NullForgivingDemo.cs

> static string? name; // name은 null이 될 수 있음
> static void PrintName() => 
.     Console.WriteLine(name!.ToUpper()); // name이 null이 아님을 보장함
> PrintName(); // 실행하면 NullReferenceException 발생
System.NullReferenceException: Object reference not set to an instance of an object.
> name = "Park";
> PrintName(); // 정상 출력: "Park" -> "Park".ToUpper() -> "PARK"
PARK
> 
PARK

이 예제에서 NullForgivingDemo 클래스의 namestring?으로 선언되어 **null이 될 수 있는 값(nullable reference type)**을 가질 수 있습니다. 그러나 PrintName 메서드에서 name!.ToUpper()를 호출하면서 !. 연산자를 사용하여 컴파일러에게 이 변수가 null이 아님을 확신하도록 명시하고 있습니다.

하지만 !. 연산자는 단순히 컴파일러의 경고를 무시할 뿐, 런타임 시 null 값이 들어있다면 여전히 NullReferenceException이 발생할 수 있습니다. 따라서 !. 연산자를 사용할 때는 실제로 변수가 null이 아님을 확신할 수 있는 경우에만 사용해야 합니다.

장 요약

C# 프로그래밍에서 가장 많은 에러를 발생시키는 부분 중 최고를 꼽으면 바로 (Null) 관련된 에러입니다. 특정 개체가 참조되지 않은 상태로 사용되면 반드시 에러가 발생됩니다. 이러한 널 관련 에러를 잡으려면 반드시 null 값이 아닌 실제 값으로 초기화하고 null에 대한 확인을 널 병합 연산자널 조건부 연산자를 사용하여 null 대신에 기본 값 등으로 초기화해 사용하는 것을 권장합니다. 널 관련 연산자는 널 처리에 대해서 if 문을 사용하지 않고 식을 사용하여 처리할 수 있도록 도움을 줍니다. 처음에는 어려울 수 있지만 많은 연습으로 이 2가지 연산자에 대해 확실히 사용법을 익혀두어야 합니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com