Nullable Type과 Yield 키워드에 대한 이해

.NET Framework 2.0에서 새롭게 추가된 부분은 매우 많습니다. 제일 잘 한 부분이라고 느껴지는 것은 제네릭 관련 부분입니다. 하지만 제네릭 못지 않게 편리해진 부분이 더 있는데 바로 Nullable Type과 Yield 키워드에 대한 사항입니다.


Nullable Type이란?


Nullable Type은 System.ValueType에 대해 보완 관계를 완성하는 일종의 보조 도구입니다. 사실 Nullable Type이라는 이름을 사용하지 않았을 뿐 이와 같은 일을 우리는 관습적으로 익혀왔습니다. 바로 System.Object로 형변환하는 작업을 일컬어 Boxing과 Unboxing을 하였다고 이야기했으며, 알고 계시다시피 System.Object로 Boxing을 할 경우 그 안에 들어있는 것이 Null 참조일 수도 있었습니다.


하지만 Nullable Type은 단지 System.Object로 Boxing하는 것보다 한 단계 더 앞의 것입니다. 단지 Null 참조를 대입할 수 만 있는 것이라면 Null 값이 아닌 경우를 좀 더 매끄럽게 핸들링하기 위해서 해줘야 할 일이 부수적으로 많았습니다. (물론, 아이디어를 짜 내어 간단한 메서드를 활용해서 Null 값이 들어있는 Object형의 경우 기본 값을 받아서 다시 반환하는 식의 디자인 패턴도 사용하곤 했습니다만.)


System.Nullable<T> 라는 좀 특별한 구조체를 통하여 이것을 가능하게 합니다. 눈치 빠르신 분들은 예상하셨겠지만 T는 Generic Parameter입니다. 물론 ValueType에 대한 것만을 다룰 것이기에 당연히 T는 where T: struct 라는 제약이 걸려있을 것도 예측할 수 있을 것입니다. (where 절에서 struct라는 것을 지정해야 ValueType을 가리키는 것으로 의미가 통한다는 것은 Generic을 써보신 분은 아실 것입니다.)


System.Nullable<T>의 멤버들을 살펴보면 재미있는 것이 있습니다. 우선 이 구조체의 생성자를 보면 구조체이기 때문에 미리 정의된 받을 인자가 없는 기본 생성자와 ValueType의 값을 하나 받도록 되어있는 생성자 버전 두 가지입니다. 인자 없이 그냥 생성자를 호출하는 경우 이것은 null 참조를 대입한 것과 동일하게 다루어집니다. 하지만 어떤 값을 줄 경우에는 null 값이 아니라 구체적인 값이 있는 것으로 다루어집니다. 이것을 구분하는 멤버가 HasValue 프로퍼티입니다.


그럼 System.Nullable<T>에서 값을 가져오는 방법은 무엇일까요? 막바로 보이는 Value 멤버의 getter 메서드를 호출하는 방법이 있습니다. 하지만 여기에는 규칙이 있는데, HasValue 프로퍼티의 값이 false로 나오는 경우는 여기에 Null 값을 대입한 것과 동일한 상태를 의미합니다. 이러한 경우 Value의 값 대신 예외가 Throw됩니다. 만약 값이 존재한다면 그 값이 정확히 반환될 것입니다.


그렇다면 값을 얻기 위해서 꼭 위와 같은 방법으로 접근하기 위해서 try – catch 구문을 써야할까요? 물론 아닙니다. 다른 메서드를 살펴보도록 하죠. 바로 GetValueOrDefault 메서드입니다. 이 메서드는 HasValue의 값이 무엇이든 가리지는 않습니다. 이 메서드는 또 다시 두 가지 버전으로 재정의되어있는 것을 보실 수 있습니다. T에서 지정한 형식의 인수를 받는 버전과 그냥 호출하는 버전의 차이입니다.


T에서 지정한 형식의 인수를 받는 버전의 경우 HasValue가 False로 나타날 경우 받았던 인수를 다시 반환합니다. 이것이 의미하는 것은 사실 간단합니다. 어떤 값도 아니고 의미없는 값을 넣었다면 이것을 구분할 수 있는 지역적인 수준의 코드 값이 되는 것입니다. 반면 인수를 받지 않는 버전의 경우 Generic에서 사용하는 문법적 기능 중 기본 값 반환에 의거하여 값을 반환합니다. 만약 int 형식으로 형식 인수 T를 지정했다면 컴파일러의 판단에 의하여 0을 반환할 것입니다. 0이라는 값을 넣은 경우와는 구분이 되지 않기 때문에 HasValue 프로퍼티를 검사해야 논리적인 오류를 피할 수 있을 것입니다.


Nullable Type을 사용하시면서 아셔야 할 특별한 제약 조건이 하나 있습니다. 다른 것은 아니며 Nullable Type은 그 자신을 다시 포함할 수 없다는 점입니다. 즉, 재귀적으로 Nullable Type을 쌓아올릴 수는 없음을 뜻합니다. 이 점만 유의하시면 어려운 점은 없을 것입니다.


Nullable Type 그 자체는 사실 누군가 만들어도 만들만한 물건입니다. 하지만 Nullable Type의 진가는 C#의 경우 다음 두 가지 문법적인 기능으로 발휘됩니다.


? 심벌: ValueType 형식명 뒤에 붙여줌으로서 Nullable<형식명> 표기를 줄입니다. 그리고 한 단계 더 나아가 하나의 독립적인 형식명으로서 인정되며 = null의 대입을 가능하게 합니다.


다음은 예제 코드입니다.


int? n = 123;
n += (int?)1;

int 형식과 호환성이 있지만 int? 라는 형식이 새로 생성된 것으로 이해하더라도 무방합니다. (사실 Generic에 인수를 대입한다는 사실이 그런 모양이기는 합니다.) 당연히 저 코드가 실행된 후의 n의 내부 값은 124가 될 것입니다.


?? 연산자: ?? 연산자는 결론부터 말씀드리면 이전 버전에서 수행하던 다음의 코드를 축약한 것입니다. (m_testObject가 ValueType이 아닐 경우를 예로 들어봤습니다.)


if(m_testObject == null)
  m_testObject = new object();

// 위의 코드를 이제는 한 줄이면 씁니다.
m_testObject = m_testObject ?? new object();


눈치가 빠르신 분들은 짐작하셨겠지만 ?? 연산자가 의미하는 조건문은 ?? 연산자의 앞에 오는 조건식 또는 인스턴스가 null 참조일 경우 ?? 다음에 오는 조건식 또는 인스턴스를 실행하는 것입니다. ValueType이 아닐 경우에 쓰이기도 하지만 위의 코드는 Nullable Type에 대해서도 유효하다는 사실에 주목할 필요가 있습니다.


Nullable Type에 대한 이야기는 여기서 간단히 마치도록 하고 이번엔 Yield 키워드에 대해서 살펴보도록 하겠습니다.


Yield 키워드란?


미리 중요한 힌트를 말씀드리면 Yield 키워드는 System.IEnumerable과 관련이 있으며 foreach와도 밀접하게 관련되어있습니다. 바로 반복에 관한 기능을 문법적으로 포장한 것입니다. 어려워 보이고 복잡해 보이는 기능일 수 있지만 잘 활용하면 컬렉션을 선택적으로 만드는 데에 있어서 자칫 복잡해 질 수 있는 코드를 깔끔하게 정리하는 데 일조를 하도록 할 수 있습니다.


다음의 코드를 살펴보면서 간단히 yield return과 yield break의 기능을 살펴보도록 하겠습니다.


using System;
using System.Collections.Generic;

public static class Program
{
  public static IEnumerable<string> GetMessages()
  {
   yield return “H”;
   yield return “ello”;
   yield return ” “;
   yield return “W”;
   yield return “orld”;
   yield return “!”;
   yield return Environment.NewLine;
   yield break;
  }

  [MTAThread()]
  public static int Main(string[] arguments)
  {
   foreach(string eachString in GetMessages())
      Console.Write(eachString);

   return 0;
  }
}


코드를 실행해 보시면 아시겠지만 yield return은 그냥 return을 쓰는 것과는 다르게 “쌓아둔다”라는 느낌을 느낄 수 있습니다. 짐작하신 그대로 코드를 실행해보면 yield return 문으로 기록해 놓은 문자열들이 그대로 IEnumerable<string> 형식으로 반환되어 콘솔에 Hello World! 라는 문자열로 나타나는 것을 볼 수 있습니다. 그렇다면 yield break 문은 당연히 쌓아놓은 yield return 문의 데이터들을 한꺼번에 컬렉션으로 변환시켜 문을 종료하는 것으로 이해할 수 있습니다.


yield 키워드를 사용하여 컬렉션을 만들기 위해서는 물론 IEnumerable 또는 IEnumerable<T> 형식으로 반환해야 한다는 조건이 붙으며 한 가지 제약이 더 붙습니다. .NET 2.0에서 새로 추가된 것 중 익명 메서드 안에서는 위와 같이 yield 키워드를 사용할 수 없다는 점입니다.


ps. 태터툴즈 클래식에서는 제네릭이라는 것을 표기하기 위해서 반드시 HTML 엔티티로 꺽쇠 기호를 나타내야 하는군요. ^^;

댓글 남기기