Boxing과 Unboxing의 모든 것

오늘 강좌는 Boxing과 Unboxing이라는 개념에 대해서 살펴볼 까 합니다. 이것은 일전의 “형 변환을 기반으로 하는 리플렉션”과 관련성이 있는 내용입니다. 그리하여 실질적인 내용을 따져보면 System.ValueType에 관한 특수한 리플렉션 기술이 Boxing과 Unboxing에 해당됩니다.


System.ValueType 형식은 메타 데이터 기반이 아닌 “낮은 수준”의 데이터 형식들의 추상 형식입니다. 여기로부터 파생되었다고 알려지는 주요 데이터 형식으로는, System.Byte, System.Int16, System.Int32, System.Int64, System.Float, System.Double 등이 있습니다. 하지만 이러한 데이터 형식들은 메타 데이터 기반이 아닙니다. 정확한 풀이로는 힙에 할당되며 크기 또한 자유자재로 변형될 수 있는 형식들입니다. 메타 데이터로 표기하는 것은 엄청난 오버로드를 수반하게 되므로 부적절합니다. 하지만 .NET Framework는 이를 매끄럽게 처리하게 되어있는데 그것이 Boxing과 Unboxing이라 불리우는 기술입니다.


Boxing과 Unboxing의 의미 풀이부터 해보도록 합니다. 두 단어 모두 Box 라는 키워드를 포함하는데, 쉽게 생각할 수 있습니다. 힙에 할당된 연속적인 데이터를 편리하게 관리하기 위하여 포장을 해놓는다 라는 의미에서 Box를 떠올리면 쉽습니다. 즉, 힙에 할당된 연속적인 데이터를 메타 데이터가 이해할 수 있는 형태로 포장해준다는 의미입니다. 그러면 실제 코딩을 살펴보도록 하지요.


int i = 123;


object o = (object)i;


위의 두 코드를 살펴보면, 사실 아무런 의미는 없습니다. 하지만 이것이 Boxing의 대표적인 예입니다. 이러한 코딩이 가능함으로서 얻을 수 있는 이점이 대단히 큽니다. Boxing을 가장 잘 활용하는 예는 ADO .NET 관련 클래스들입니다. 데이터 베이스 시스템이 질의에 대한 결과로 반환하는 데이터 형식을 한꺼번에 다루기 위한 방법으로 Boxing과 Unboxing을 복합적으로 활용합니다.


i 라는 변수에 123이라는 정수 값을 대입했습니다. 그 다음, o 라는 하나의 추상 객체를 선언하여 형식 변환을 취하였습니다. 코드 상에서는 분명히 형식 변환으로 표기되었습니다만 사실 이 둘은 형식 변환이 일어날 수 없는 관계입니다. object는 메타 데이터로 표기되는 데이터만을 다룰 수 있지만 int는 힙에 할당되는 연속적인 데이터 스트림입니다. 서로 관계가 없지만 이것이 가능했던 이유가 바로 Boxing입니다.


int x = (int)o; // Okay


long y = (int)o; // Okay


short z = (int)o; // Error!


x는 i와 같은 int 형식이고, y는 int 형식보다 큰 범위의 수를 다룰 수 있는 long 형식이며, z는 int 형식보다는 작은 범위의 수를 다루는 short 형식입니다. 세 동작 모두 object 형식을 int 형식으로 바꾸는 동시에 Unboxing을 수행하게 되었습니다. 즉, object 형식으로 포장된 데이터의 원래 내용물을 대입한 것입니다. 하지만 x, y와는 다르게 z는 컴파일 오류를 낼 것입니다.


x는 손실 변환이 일어나지 않았으며 원래의 형식 그대로를 수용하였습니다. y는 원래의 i가 요구하던 정수 범위보다 더 큰 정수 범위를 지원하게 되어 확장 변환이 일어났습니다. 이 경우 두 가지 의미로 해석이 가능한데, 말 그대로 가능성을 위하여 예약된 확장 변환일 수 있지만 반대로 불필요한 공간이 더 많이 할당된 오버헤드 변환이기도 합니다. 하지만 z는 컴파일 오류를 냅니다. 손실 변환으로 다루어질 수도 있겠지만 Unboxing의 정의에 의하면 원래 가지고 있던 데이터 형식보다 범위가 더 적어졌으므로 메모리 구조와는 일치하지 않는 것으로 해석됩니다.


int a = o as int; // Error


형 변환에 사용하는 as 키워드로 변환을 시도해 보았습니다. 하지만 오류가 나게됩니다. 왜 일까요? as 형식으로 형변환이 가능하다는 것은 메타 데이터에 한정된 내용입니다. 따라서, System.ValueType으로부터 상속받은 모든 형태의 값 형식에서는 as로 형변환을 하거나 as로 Unboxing되지 않게 되었습니다.


as를 사용하고자 하였던 의도가 예외를 Throw 하지 않고 null을 대입하려 했던 것이었다면 null을 대입하지 않는 대신 다음과 같은 방법으로 처리하는게 좋습니다.


try { int a = (int)o; }


catch { /* 예외 처리 코드 */ }


오늘 강좌가 많은 도움이 되셨기를 바랍니다.

댓글 남기기