값 형식의 데이터를 참조 형식으로 변환하는 것을 박싱이라고 하며, 그 반대를 언박싱이라고 한다. 이에 대해 유의할 점 두가지가 있다.

첫번째는 단순히 스택에서 힙으로의 값 복사가 박싱은 아니라는 것이다. 클래스의 필드로 값 형식이 선언됐다고 생각해보자. 객체는 힙 메모리에 할당되므로 그 필드 역시 힙의 연속된 메모리에 존재할 것이다.

그렇다고 그 값 형식의 필드가 박싱된 것은 아니다. 반대로 그 필드의 값을 참조 형식으로 변환한다면 그것 역시 박싱이다. 힙 메모리 내부에서도 박싱이 일어날 수 있다는거다.

두번째는 참조 형식을 값 형식으로 바꾸는게 무조건 언박싱은 아니라는 것이다. 언박싱은 박싱된 데이터를 다시 값 형식으로 돌려놓는 것이다. 즉 박싱이 있어야 언박싱이 있을 수 있다.

C#에서 struct는 new로 할당해도 stack영역에 할당된다.

하지만 class 안에 존재하는 struct의 경우 class가 참조형식이므로 힙영역에 같이 할당된다.

박싱/언박싱을 왜 사용할까?

대표적인 참조 형식에는 'object'가 있다. Object 클래스는 모든 타입의 어버이이다. 즉 모든 타입은 object 타입으로 형변환을 할 수 있다는 것이고, int, struct와 같은 값 형식의 타입도 object 타입으로 형변환이 가능하다는 말이다.

아래 코드는 박싱과 언박싱의 예시이다.

class Program
{
    static void Main(string[] args)
    {
        BoxingTest(1, "2", '3');
    }

    static void BoxingTest(params object[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            if (args[i] is int)
            {
                int n = (int)args[i];
                Console.WriteLine(n + " is int");
            }
            else if (args[i] is char)
            {
                char c = (char)args[i];
                Console.WriteLine("'" + c + "' is char");
            }
            else if (args[i] is string) // string은 애초에 참조 형식이므로 따지고보면 박싱이 아니긴 하다.
            {
                string s = (string)args[i];
                Console.WriteLine('"' + s + "\\" is string");
            }
        }
    }
}

값 형식의 타입은 BoxingTest의 인자로 넘겨지면서 object 타입으로 박싱된다. 그리고 원래의 타입으로 다시 언박싱되어 사용된다.

BoxingTest 메서드는 object 타입을 매개변수로 받는다. 이는 모든 타입을 인자로 활용할 수 있음을 뜻한다. 데이터를 사용할 때 원래의 타입으로 돌려놓는 과정이 필요하지만 이를 잘 사용하면 상당히 범용적인 코드가 탄생한다.

박싱/언박싱은 과연 좋은 것일까?

최적화 관점에서 박싱과 언박싱은 지양해야 되는 기법이다.

사실 박싱과 언박싱은 내부적으로 상당한 오버헤드를 감수하고 사용해야 된다.