오늘 주제는 Nullable 이 되시겠다.
Console로 TextRPG를 만들면서 C# 코딩을 하는에 Nullable에 대한 경고가 엄청 많이 나오길래 이참에 익숙해질겸 해서 블로그에 서술해 본다.
Nullable 타입이라 함은 C#에서는 null 을 가질 수 없는 값형식에 null값을 가질 수 있도록 해주는 녀석이다.
예를 들어
static int num4;
static int? num5;
static void NullableTest()
{
int num1 = 0;
int? num2 = null;
Nullable<int> num3 = null; // int? 와 같은 의미임
Console.WriteLine("num1: {0}", num1); // 컴파일 에러
Console.WriteLine("num2: {0}", num2); // null
Console.WriteLine("num3: {0}", num3); // null
Console.WriteLine("num4: {0}", num4); // 0
Console.WriteLine("num5: {0}", num5); // null
}
전역변수로 초기값을 선언하지 않는다면 무조건 0이 기본값이다. 하지만
Nullable 형식으로 선언을 해준다면 초기화 하지 않을 경우기본값은 null 이 된다.
지역역변수도 int 타입의 변수라면 초기값을 무조건 숫자로 정해줘야하지만, Nullable인 경우는 그냥 null로 초기값을 설정해줘도 된다.
이제 값 형식과 참조 형식에서의 사용하는 방법을 알아보자.
여기에 소스 코드가 있다.
static TestClass? test1;
static TestClass test2;
static TestStruct? test3;
static TestStruct test4;
static void NullableTest()
{
Console.WriteLine(test1?.Name);
Console.WriteLine(test2.Name);
Console.WriteLine(test3?.Name);
Console.WriteLine(test4.Name);
}
해당 소스코드를 실행했을때 예상이 되는가? 실행하기 전까진 아마 조금 애매할 수 있다. 정답을 보자면
Console.WriteLine(test1?.Name); // null (null이면 null 값을 줌)
Console.WriteLine(test2.Name); // 에러 (반드시 인스턴스를 할당 받아야됨)
Console.WriteLine(test3?.Name); // null (null이면 null 값을 줌)
Console.WriteLine(test4.Name); // null (test4는 null이 아니지만 Name이 null 이기때문에 null임
이런식으로 실행이 된다. class는 참조 타입이라 알겠고 struct는 인스턴스를 생성도 안했는데 생성이 된것 처럼 보인다. 이유는 struct 타입이 값타입 형식으로 되어 있기 때문인데 중요한건 그게 아니니 일단 넘어가고 이제 이상한걸 찾아보면 struct와 class를 Nullable로 사용하면 사용방법이 조금 차이가 있다.
static TestClass? test1;
static TestStruct? test3;
static void NullableTest()
{
Console.WriteLine($"{test1 == null}, {test1}, {test1?.Name}");
Console.WriteLine($"{test3.HasValue}, {test3.Value} : {test3?.Name}");
}
위 코드처럼 struct나 값 형식의 타입의 경우 Nullable로 래핑해서 사용하게 되면 ? 연산자를 사용하지 않는 이상 Nullable 타입으로 사용할 수 있게 된다. 그래서 test3.HasValue로 값이 null인지 아닌지 확인할 수 있고, test3.Value로 실제 값 타입을 가져올 수 있게 된다.
그리고 class는 타입 자체가 참조 타입이기 때문에 기본적으로 null이 허용이라 Nullable로 사용하더라도 그냥 class 내부에 접근이 가능하다.
지금까지 struct만 가지고 예시를 들었지만 값 타입(int, double, float, bool 등)은 모두 struct와 사용하는 법이 같다.
public class TestClass
{
public string Value;
public TestClass(string value)
{
Value = value;
}
public void Run()
{
Console.WriteLine("TestClass Run " + Value);
}
}
public struct TestStruct
{
public string Value;
public TestStruct(string value)
{
Value = value;
}
public void Run()
{
Console.WriteLine("TestStruct Test" + Value);
}
}
static TestClass? test1;
static TestStruct? test3;
static void NullableTest()
{
// test1 이 null 이면 새로운 인스턴스를 생성하여 test11에 할당
TestClass? test11 = test1 ?? new TestClass("??로 할당");
test11.Run(); // 당연히 생성 되었으므로 실행 가능
test1?.Run(); // test1은 null이기 때문에 실행 안함
// test1 이 null 이면 새로운 인스턴스를 생성해서 test1에다가 할당
test1 ??= new TestClass("??=으로 할당");
// null 체크 후 실행
if (test1 != null) test1.Run(); // 당연히 실행이 된다.
// 위에랑 똑같은 과정임..
TestStruct? test33 = test3 ?? new TestStruct("??로 할당");
test33?.Run();
test3?.Run();
test3 ??= new TestStruct("??=으로 할당");
// test3.HasValue를 통해 null체크를 하고 Value에서 실제 값을 가져와 실행
if (test3.HasValue) test3.Value.Run(); // 당연히 실행이 된다.
// 참고로 if (test3 != null) 방식으로 null 체크 삽가능
if (test3 != null) test3.Value.Run();
// 실행 결과
// TestClass Run ??로 할당
// TestClass Run ??=으로 할당
// TestStruct Test??로 할당
// TestStruct Test??=으로 할당
// TestStruct Test??=으로 할당
}
struct,값 타입 과 참조 타입은 Nullable 사용방법이 다르므로 참조 타입은 평소에 하던데로 null 체크를 하고 값 타입, struct는 주의해서 사용하자.