널(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 허용 연산자

null 허용 연산자(!)로 컴파일러 경고 무시하기: NullForgivingDemo.cs

코드: 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가지 연산자에 대해 확실히 사용법을 익혀두어야 합니다.

더 깊이 공부하고 싶다면
DevLec에서는 실무 중심의 C#, .NET, ASP.NET Core, Blazor, 데이터 액세스 강좌를 단계별로 제공합니다. 현재 수강 가능한 강좌 외에도 더 많은 과정이 준비되어 있습니다.
DevLec.com에서 자세한 커리큘럼을 확인해 보세요.
DevLec 공식 강의
C# Programming
C# 프로그래밍 입문
프로그래밍을 처음 시작하는 입문자를 위한 C# 기본기 완성 과정입니다.
ASP.NET Core 10.0
ASP.NET Core 10.0 시작하기 MVC Fundamentals Part 1 MVC Fundamentals Part 2
웹 애플리케이션의 구조와 MVC 패턴을 ASP.NET Core로 실습하며 익힐 수 있습니다.
Blazor Server
풀스택 웹개발자 과정 Part 1 풀스택 웹개발자 과정 Part 2 풀스택 웹개발자 과정 Part 3
실무에서 바로 활용 가능한 Blazor Server 기반 관리자·포털 프로젝트를 만들어 봅니다.
Data & APIs
Entity Framework Core 시작하기 ADO.NET Fundamentals Blazor Server Fundamentals Minimal APIs
데이터 액세스와 Web API를 함께 이해하면 실무 .NET 백엔드 개발에 큰 도움이 됩니다.
VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com