개인적인 경험으로는 프로그램을 할 때 오타를 제외한 대부분의 오류는 null 체크를 제대로 하지 않는 경우에 발생하는 것 같다. C#에서는 null을 다루는 형식과 연산자가 따로 있다고 해, null의 개념과 관련 내용을 정리해보겠다.
널(null) 값
null은 의미 있는 값이 없음을 의미한다.
단, 아무것도 없는 "Empty"의 개념보다 알 수 없는 "undefined"의 개념에 더 가깝다.
- null을 주로 접하게 되는 경우
- 참조형 변수를 선언한 후 초기화하지 않은 경우
- 알려지지 않는 값을 가리키고 있어 의미 없는 경우
- 변수 또는 개체가 생성된 후 아무런 참조를 하지 않은 경우
- 가리키고(참조하고) 있던 데이터가 소실되어 가리키는 데이터가 의미 없어진 경우
즉, 주로 무엇인가를 참조하는 상황에서 참조하는 것이 없거나 의도치 않는 참조인 경우 null 값을 가진다고 볼 수 있다.
Nullable<T> 형식 : null 가능 형식
기본적으로 원래 참조 형식인 string과 object를 제외한 값 형식인 int, float 등은 null값을 저장할 수 없다. 그러나 이 형식을 이용한다면 null값을 가지도록 할 수 있다.
- bool : true, false
- Nullable<bool> : true, false, null
Nullable<T> 형식을 줄여서 표현하는 방법이 존재한다. 아래의 두 표현은 같은 표현이다.
//Nullable<T> 줄임 표현 : <타입>?
Nullable<int> nullInt1;
int? nullInt2;
Nullable<T> 형식이 가지는 속성은 다음과 같다.
- HasValue : 값이 있으면 true, 없으면(null이면) false
- Value : 값을 반환
- GetValueOrDefault : 값 또는 기본값을 반환
class NullablePractice
{
static void Main()
{
//Nullable<bool> 형식 사용하는 2가지 방법
Nullable<bool> bin1 = null;
bool? bin2 = false;
//bin1은 null이므로 false
Console.WriteLine(bin1.HasValue);
//bin2는 값(true)이 있으므로 true
Console.WriteLine(bin2.HasValue);
}
}
//출력 : false true
?? 연산자 : null 병합 연산자(null coalescing operator)
기존에 ?를 쓰는 연산자인 삼항 연산자와 구조가 몹시 유사하다. 값이 null이면 값 대신 설정한 값을 전달해준다.
- ??연산자 형식 : (타겟 변수) = (저장할 변수) ?? (null일 시 저장할 값);
class NullablePractice
{
static void Main()
{
string nullStr = null;
//??연산자를 사용
string msg = nullStr ?? "null이면 이 문장을 저장";
//nullStr이 null이므로 오른쪽 문장을 저장해 출력
Console.WriteLine(msg);
}
}
//출력 : null이면 이 문장을 저장
- ?연산자(삼항 연산자) 또는 if문을 활용해 비슷하게 구현할 수 있다.
class NullablePractice
{
static void Main()
{
string nullStr = null;
//??연산자를 사용
string msg1 = nullStr ?? "null이면 이 문장을 저장";
//? 연산자를 사용해 ??연산자 구현
string msg2 = (nullStr == null) ? "?연산자로 구현" : nullStr;
//if문을 통해 ??연산자 구현
string msg3 = nullStr;
if (nullStr == null)
{
msg3 = "if문으로 구현";
}
//nullStr이 null이므로 대체값을 출력
Console.WriteLine(msg1);
Console.WriteLine(msg2);
Console.WriteLine(msg3);
}
}
//출력 : null이면 이 문장을 저장 ?연산자로 구현 if문으로 구현
??연산자를 활용해 null인 값을 원하는 값으로 대체할 수 있음을 확인했다. 하지만 변수가 많아진다면 대체값을 하나씩 설정하는 것이 번거로워진다. 이때 default라는 키워드를 사용하는 것으로 작업을 단순화할 수 있다.
- ??연산자 + default 키워드
class NullablePractice
{
static void Main()
{
int? nullInt = null;
//default(T)는 자동적으로 저장되는 변수의 타입에 맞춰진다.
int i1 = nullInt ?? default(int);
int i2 = nullInt ?? default;
//int의 기본값은 0
Console.WriteLine(i1);
Console.WriteLine(i2);
}
}
//출력 : 0 0
- ??연산자 + Nullable<T> 형식
- Nullable<T> 형식과 ??연산자를 활용해 알 수 없는 값에 대해서도 제어를 할 수 있다.
class NullablePractice
{
static void Main()
{
bool? unknown = null;
//true와 null만 통과하도록 if문 조건 설정
if (unknown ?? true)
{
Console.WriteLine("통과");
}
}
}
//출력 : 통과
?.연산자 : null 조건부 연산자(null conditional operator)
Nullabe<T> 형식의 변수가 null인지 확인해서 null이면 null을, 아니면 메서드를 실행한다. 따로 명시적으로 선언을 하지 않더라도 string, object와 같이 null이 가능한 변수나 개체는 사용 가능하다.
- ?.연산자 형식 : (Nullable<T> 형식의 변수)?.(메서드);
class NullablePractice
{
static void Main()
{
double? d1 = null;
double? d2 = 3.141592;
//변수가 null이면 null 반환, 아니면 뒤의 메서드 정상 실행
Console.WriteLine(d1?.ToString());
Console.WriteLine(d2?.ToString());
}
}
//출력 : null(화면에는 표시되지 않음) 3.141592
- ?.연산자는 명시적으로 선언하지 않은 변수(string)에서도 사용 가능
class NullablePractice
{
static void Main()
{
string diary1 = null;
string diary2 = "HappyHappy";
Console.WriteLine(diary1?.Length);
Console.WriteLine(diary2?.Length);
}
}
//출력 : null(화면에는 표시되지 않음) 10
- ?.연산자는 ?[ ]형태로도 사용 가능
class NullablePractice
{
static void Main()
{
int?[] arr = {111, null, 999};
Console.WriteLine(arr?[0]);
Console.WriteLine(arr?[1]);
Console.WriteLine(arr?[2]);
}
}
//출력 : 111 null(화면에는 표시되지 않음) 999
- ?.연산자 + 컬렉션(제네럴) 클래스
using System.Collections.Generic;
class NullablePractice
{
static void Main()
{
List<string> list = null;
//list가 null이므로 null 반환
Console.WriteLine(list?.Count);
list = new List<string>();
list.Add("item1");
list.Add("item2");
list.Add("item3");
//list가 null이 아니므로 Count 실행
Console.WriteLine(list?.Count);
}
}
//출력 : null(화면에는 표시되지 않음) 3
- ?.연산자 + ??연산자
- 두 연산자를 함께 사용하는 것으로 좀 더 범용적이고 안전한 코드가 완성되었다.
using System.Collections.Generic;
class NullablePractice
{
static void Main()
{
List<string> list = null;
//list가 null이므로 null 반환 -> ?? 연산자에 의해 0으로 대체
Console.WriteLine(list?.Count ?? 0);
list = new List<string>();
list.Add("item1");
list.Add("item2");
list.Add("item3");
//list가 null이 아니므로 Count 실행
Console.WriteLine(list?.Count ?? 0);
}
}
//출력 : 0 3
null 값은 제대로 체크하지 않으면 오류가 많이 발생하고, 나중에 찾으려하면 찾기도 어려우니 평소에 null 관련 형식과 연산자를 이용해 이중 체크, 안전 장치를 설치해야겠다.
'C#' 카테고리의 다른 글
[C#] 대리자(Delegate; 델리게이트)와 무명 메서드(+람다식 기초) (1) | 2024.04.26 |
---|---|
[C#] 참조 매개 변수, ref와 out의 차이점 (0) | 2024.04.25 |
[C#] 값 형식과 참조 형식, 박싱과 언박싱(힙 메모리), is 연산자와 as 연산자 (0) | 2024.04.25 |
[C#] 다차원 배열과 가변 배열(C/C++ 문법과 차이점) (0) | 2024.04.25 |
[C#] 제네릭(Generic) 클래스 (0) | 2024.04.24 |
[C#] 컬렉션(Collection) 클래스 (0) | 2024.04.24 |
[C#] Rider 내 실습용 콘솔 솔루션 생성 & 유용한 문자열 형식, 표기법 (1) | 2024.04.23 |
[C#] 초기 개발 환경 설정(Rider) 및 유니티 연동 + SDK 8.0 설치 (0) | 2024.04.23 |