널(Null) 다루기
프로그래밍 언어에서 널(Null, NULL, null, nil)은 아무것도 없는 상태를 나타냅니다. 이번에는 널 관련 기능을 정리하는 시간을 갖도록 하겠습니다.
> // null: 아무 것도 없음을 의미하는 리터럴, 개체가 아무 것도 참조하지 않음을 null 참조라 함
null 값
이미 우리는 null
을 많이 사용해 왔습니다. 다음 참고 그림의 3번째처럼 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>
제네릭 형식을 사용하면 됩니다.
bool
과 Nullable<bool>
의 차이점은 다음과 같습니다.
bool
형식은true
와false
를 갖습니다.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
대신에 -1
을 defaultValue
에 할당해 줍니다. 이러한 코드를 사용함으로써 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 허용 연산자로 표기가 되어 있어 박용준 강사도 이걸로 표기합니다.
코드: 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
클래스의 name
은 string?
으로 선언되어 **null이 될 수 있는 값(nullable reference type)**을 가질 수 있습니다. 그러나 PrintName
메서드에서 name!.ToUpper()
를 호출하면서 !.
연산자를 사용하여 컴파일러에게 이 변수가 null이 아님을 확신하도록 명시하고 있습니다.
하지만 !.
연산자는 단순히 컴파일러의 경고를 무시할 뿐, 런타임 시 null 값이 들어있다면 여전히 NullReferenceException
이 발생할 수 있습니다. 따라서 !.
연산자를 사용할 때는 실제로 변수가 null이 아님을 확신할 수 있는 경우에만 사용해야 합니다.
장 요약
C# 프로그래밍에서 가장 많은 에러를 발생시키는 부분 중 최고를 꼽으면 바로 널(Null) 관련된 에러입니다. 특정 개체가 참조되지 않은 상태로 사용되면 반드시 에러가 발생됩니다. 이러한 널 관련 에러를 잡으려면 반드시 null
값이 아닌 실제 값으로 초기화하고 null
에 대한 확인을 널 병합 연산자와 널 조건부 연산자를 사용하여 null
대신에 기본 값 등으로 초기화해 사용하는 것을 권장합니다. 널 관련 연산자는 널 처리에 대해서 if
문을 사용하지 않고 식을 사용하여 처리할 수 있도록 도움을 줍니다. 처음에는 어려울 수 있지만 많은 연습으로 이 2가지 연산자에 대해 확실히 사용법을 익혀두어야 합니다.