참조 타입의 경우 값이 없을 때 null을 이용해 값이 없음을 나타낼 수 있지만 값 타입은 null을 사용할 수 없다. 아래 코드와 같이 어떤 함수 내에 특정 조건에 있을때에는 null을 사용할 수 없기 때문에 INF에 해당하는 특정 숫자를 사용해야 한다. 하지만, 0이라는 반환값이 실제값을 의미하는 것인지 값이 없음을 의미하는지는 알 수 없다.
public static int foo()
{
if (...) return 0;
return 1;
}
C#에는 이러한 문제를 해결할 수 있도록 Nullable이라는 타입이 존재한다. 문자 그대로 null이 될 수 있는 형식을 의미하며 값 타입으로 사용된다. 사용하는 방법은 아래 코드와 같다. Nullable<T>형태로 사용할 수 있다.
또한, 긴 자료형을 짧게 표현하는 방법으로 int?과 같은 방법이 존재한다. 아래 예제를 보면 Nullable<int>와 int?가 같은 것을 의미하는 것을 볼 수 있다.
public static void Main(string[] args)
{
// string: reference type
string s1 = "Hello";
string s2 = null;
// int: value type
int n1 = 10;
Nullable<int> n2 = 10; // int? n2 = 10;
Nullable<int> n3 = null; // int? n3 = null;
}
처음 foo예제에서 Nullable를 적용한 코드는 아래와 같다. 반환형을 바꿔주면 return null이 가능하기 때문에 foo를 사용하는 외부 함수에서 값이 없음을 null로 구분할 수 있다.
public static Nullable<int> foo()
{
if (...) return null;
return 1;
}
Nullable<T>는 struct 내부에 T와 값이 있는지를 체크하는 bool변수를 같이 둔 형태로 구성되어 있다. 즉, null체크를 내부에 존재하는 bool변수로 표현하는 구조로 되어있다.
Nullable타입은 기본적으로 대입연산과 산술연산을 지원한다. 그렇다면, int?와 int사이의 대입연산은 어떻게 적용될까? 아래 예제를 보자.
public static void Main(string[] args)
{
int? n1 = 10;
int a1 = 20;
n1 = a1;
a1 = n1; // Error!
a1 = (int)n1; // Ok!
}
위의 코드에서 나타나는 대입연산의 특징을 정리하면 아래와 같다.
int?에 int를 대입하는 것은 가능하다.int에 int?를 대입하는 것은 에러가 발생한다.int에 int?를 대입하려면 int?를 int로 명시적 형변환을 해주어야 한다.그렇다면 산술연산을 어떻게 동작할까?
public static void Main(string[] args)
{
int? n1 = 10;
int? n2 = 20;
int? n3 = n1 + n2; // n3 is 30!
}
not-null인 int?변수에 대해서는 int변수와 같이 작동한다. 하지만, 둘 중 하나의 변수가 null상태라면 어떻게 될까? 아래 예제를 보면 null과의 연산 결과는 null이 되는 것을 알 수 있다.
public static void Main(string[] args)
{
int? n1 = null;
int? n2 = 20;
int? n3 = n1 + n2; // n3 is null!
}