[C#] 3.2 Boxing 과 UnBoxing

C#의 object 형식은 모든 형식의 기반 형식입니다. 여기에서는 값 형식들이 object 형식 변수에 대입하거나 object 개체를 값 형식 변수에 대입하여 사용할 때 어떤 메커니즘이 진행되는지 살펴보기로 합시다.

C#에서 값 형식은 구조체와 열거형으로 나눌 수 있습니다. 기본 형식에서 object와 string을 제외한 모든 기본 형식은 구조체입니다. 구조체는 여러 종류의 멤버들을 캡슐화하여 사용할 수 있습니다. 구조체는 기반 형식이 될 수 없으며 기본 생성자(매개 변수가 없는 생성자)와 소멸자를 선언할 수 없습니다. C#에서는 여러 멤버를 캡슐화하여 사용자 정의 형식을 만들 때 일반적으로 클래스를 사용하지만 단순한 경우에 구조체를 사용할 수도 있습니다.

C언어나 C++언어를 학습해 본 적이 있는 이들은 int와 같은 기본 형식들이 구조체라고 하면 이해가 가지 않을 수 있을 것입니다. 하지만, C#에서는 int는 멤버 필드와 멤버 메서드들을 캡슐화하고 있는 구조체임에 틀림이 없습니다. C#에서는 다음과 같은 표현을 할 수가 있으며 이를 통해 int도 구조체임을 알 수 있습니다.

 int 형식의 멤버들
[그림 10] int 형식의 멤버들

 C#에서 값 형식들은 스택에 할당되고 참조 형식의 개체는 관리화 힙에 동적으로 할당됩니다. 그리고 C#의 모든 형식은 object에서 파생되었다고 하였죠. 그러면 object 형식 변수에 값 형식을 대입하면 어디에 할당될까요?

다음 코드를 통해 설명할게요.

static void Main(string[] args)
{
    int a = 0;
    a = 4;
    object o = a;//Boxing
    Console.WriteLine(o.ToString());

    int b = 0;
    b = (int)o; //UnBoxing
    Console.WriteLine(b.ToString());
}

모든 value 형식은 object에서 파생된 형식이므로 object 형식 변수 o에 int 형식 변수를 대입할 수 있습니다. 이처럼 값 형식을 object에 대입하면 값을 대입 받기 위한 object 개체가 관리화 힙에 생성됩니다. 그리고 해당 개체에 대입한 형식이 int 형식이라는 것과 값을 보관합니다. 이러한 과정을 Boxing이라고 하며 내부적으로 오버헤드가 발생합니다.

object o = a;//Boxing

그리고 object 개체를 값 형식에 형 변환하여 대입을 하는 과정은 UnBoxing이라고 합니다. UnBoxing과정에서는 object 개체의 실제 형식이 형 변환 요청에 명시한 형식인지 확인하여 맞았다면 보관된 값을 반환합니다. 하지만 형식이 다르다면 예외를 발생합니다.

b = (int)o; //UnBoxing

다음은 예제를 컴파일하여 IL코드를 덤핑한 것입니다. 덤핑할 때는 Visual Studio 명령 프롬프트에서 ildasm을 수행하여 덤핑을 원하는 .NET 어셈블리를 선택하면 IL코드를 확인할 수 있습니다.

▶ ildasm을 이용하여 덤핑한 IL코드

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 코드 크기       25 (0x19)
  .maxstack  1
  .locals init ([0] int32 a,
           [1] object o)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldc.i4.4
  IL_0004:  stloc.0
  IL_0005:  ldloc.0
  IL_0006:  box        [mscorlib]System.Int32
  IL_000b:  stloc.1
  IL_000c:  ldloc.1
  IL_000d:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0012:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0017:  nop
  IL_0018:  ret
} // end of method Program::Main
명령 프롬프트와 ildasm 실행 화면
[그림 11] 명령 프롬프트와 ildasm 실행 화면

이처럼 value 형식과 object 형식 사이에는 Boxing과 UnBoxing이라는 비교적 오버헤드가 큰 작업이 요구됩니다. 대부분의 프로그램 개발에서 이러한 오버헤드가 크게 신경을 써야 할 이슈는 아니지만 신경을 써야 하는 프로그램도 있습니다. 특히, 여러 요소를 컬렉션에 보관한다면 오버헤드가 커지게 되는데 제네릭 컬렉션을 사용하면 오버헤드를 줄일 수 있습니다. 여기에서는 이에 대한 설명은 다루지 않고 8장 인터페이스와 컬렉션에서 설명하겠습니다.

이처럼 Boxing과 UnBoxing에 들어가는 오버헤드를 고려하여 프로그래밍한다면 작성하고자 하는 프로그램 내에 사용자 정의 형식을 구조체로 정의할 것인지 클래스로 정의할 것인지를 판단하기가 쉬워집니다. MSDN에서는 다음의 네 가지 조건이 만족할 때에만 구조체를 정의하라는 지침을 제시하고 있습니다.

a. 기본 형식과 유사한 단일 값을 논리적으로 나타냅니다.
b. 인스턴스 크기가 16바이트보다 작습니다.
c. 변경할 수 없습니다.
d. 자주 boxed 하지 않아도 됩니다.