목차

1. Boxing과 Unboxing의 개요

2. Boxing과 Unboxing이 성능에 미치는 영향에 관하여

 

요약

 박싱은 값 타입을 참조 타입으로 변환하는 작업을 의미하고, 언박싱은 참조 타입의 값을 값 타입으로 변환하는 작업을 의미합니다.

 

 값 타입의 변수가 저장되는 위치는 stack메모리에 위치하고, 참조 타입은 heap 메모리에 위치합니다. 박싱이 발생할 때 힙에 공간을 할당해서(box) stack에 있는 값을 복사해 넣습니다. 그리고 stack 메모리에서는 값이 저장되어있는 객체를 가리키는 힙 메모리의 주소를 저장하게 됩니다.

 

사용 예시코드

int i = 123;      // a value type
object o = i;     // boxing (박싱발생, 암묵적 형변환 가능.)
int j = (int)o;   // unboxing (언박싱 발생, 명시적 형변환을 통해서 가능하다.)

 

이것만 알아도 충분하다고 생각하지만, 공식 다큐먼트를 읽은 내용과 좀 더 내용을 추가하여 글을 작성했습니다. 공식 다큐먼트 내용을 편하게 읽고 싶은 분은 읽으시면 좋을 것 같습니다.

 

1. Boxing과 Unboxing의 개요

 Boxing은 값 타입을 object 또는 값 타입에 의해 구현된 인터페이스 타입으로 컨버팅(변환)하는 과정입니다. CLR이 value type을 boxing할 때, System.Object 인스턴스 내부에 값을 래핑하고, 관리되는 heap에 저장합니다. 

 

 Unboxing은 object에서 값 타입으로 값을 추출하는 것을 의미합니다. 박싱은 묵시적이고 언박싱은 명시적입니다. (unboxing은 캐스팅을 할 때 타입을 명시해야한다는 의미입니다.) boxing과 unboxing의 개념은 어느 type이든 object로 처리할 수 있다는 타입 시스템에 대한 C#의 통합된 시각에 대한 기반을 이룹니다.

 

Boxing

 박싱은 가비지가 수집 힙에 값 타입을 저장하는데 사용됩니다. 박싱은 값 타입을 객체 타입 또는 값 유형에 의해 구현된 인터페이스 타입으로 묵시적 변환을 하는 것입니다. 값 타입을 boxing하면 힙에 객체 인스턴스를 할당하고, 할당되어 만들어진 새로운 객체에게 값을 복사하여 넣어줍니다.

 

int i = 123;
object o = i; // 박싱은 i의 값을 객체 o에게 복사를 합니다. 그리고, 암묵적(묵시적)형 변환이 발생합니다.
object o = (object)i;  // 명시적 형변환을 시킬 수도 있지만, 이렇게 할 필요가 없습니다.

이 이미지를 보개되면, object o = i;를 수행할 때 다음과 같은 일들이 발생합니다. 

  1. 힙에 객체를 할당합니다.
  2. 힙에 할당되어 만들어진 새로운 객체에게 값을 복사하여 넣어줍니다.
  3. 변수 o는 박싱된 객체를 가리키는 주소를 가지고 있게됩니다.
class TestBoxing
{
    static void Main()
    {
        int i = 123;

        // Boxing copies the value of i into object o.
        object o = i;

        // Change the value of i.
        i = 456;

        // The change in i doesn't affect the value stored in o.
        System.Console.WriteLine("The value-type value = {0}", i);
        System.Console.WriteLine("The object-type value = {0}", o);
    }
}
/* Output:
    The value-type value = 456
    The object-type value = 123
*/

i와 o를 print해보았을 때 각각의 결과는 다릅니다. i는 456, o는 123이 출력됩니다. i의 값을 456으로 변경했지만, o와는 전혀관련이 없이 123이 출력됩니다. 이는 서로 값이 저장되어 있는 메모리의 영역이 다르다는 것을 확인할 수 있는 부분입니다.

 

Unboxing

 언박싱은 오브젝트 타입을 값 타입으로 또는 인터페이스 타입을 인터페이스로 구현된 값 타입으로 명시적 변환을 할 수 있습니다.

  1. 주어진 값 타입의 박싱된 값인지 확인하기 위해 객체 인스턴스를 확인합니다.
  2. 인스턴스의 값을 값 타입 변수로 복사합니다.

다음 코드는 boxing과 unboxing 둘다 포함된 예제입니다.

int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;   // unboxing

 

런타임에 값 타입의 언박싱이 성공하려면, 언박싱되는 항목이 중요합니다. 언박싱되는 항목은 이전에 값 타입의 인스턴스를 boxing하여 만들어졌던 객체에 대한 참조여야 합니다. null을 언박싱하려고 하면 NullReferenceException이 발생합니다. 그리고 호환되지 않는 값 유형에 대한 참조를 언박싱하려고 하면 InvalidCastException이 발생합니다.

 

 

2. Boxing과 Unboxing이 성능에 미치는 영향에 관하여

 

Boxing과 Unboxing은 성능에 좋은 영향을 미치지는 않는다.

 단순히 변수에 값을 할당하는 것과 비교해 보았을 때, boxing과 unboxing은 계산 비용이 많이 드는 과정입니다. 값 타입을 boxing했을 때, 새로운 오브젝트가 할당되고 생성되야합니다. 덜 하긴 하지만, unboxing에 필요한 캐스팅 또한 계산 비용이 많이듭니다.

 Boxing을 하기위해 1회성으로 생성된 수많은 Box는 Garbage로 변합니다. 이는 Garbage Collection이 자주 일어나는 것으로 연결되는데, GC(Garbage Collection)가 발생하면 게임의 프레임드랍이 발생합니다. 

 

즉, Garbage Collection이 많이 일어나지 않도록 Garbage가 적게 생기는 코드를 짜는것이 중요하다고 할 수 있습니다.

 

 

boxing과 unboxing에 관한 주의사항

 예를 들어 System.Collections.ArrayList와 같이 제네릭이 아닌 컬렉션 클래스에서 상당히 많은 횟수로 boxing되어야 하는 경우 값 타입을 사용하지 않는 것이 좋습니다. System.Collections.Generic.List<T>와 같은 제네릭 컬렉션을 사용하는 경우 값 형식을 boxing하지 않을 수 있습니다. Boxing 및 unboxing은 계산을 많이 해야 하는 프로세스입니다. 값 형식이 boxing되면 완전히 새로운 object가 생성되어야 합니다. 이 작업은 단순 참조 할당보다 20배나 오래 걸립니다. unboxing시 캐스팅 프로세스는 할당의 4배에 달하는 시간이 소요될 수 있습니다.

 

 ArrayList는 같은 형식의 데이터가 아니라도 담을 수 있기에, 이 과정에서 수많은 boxing과 unboxing이 일어날 수 있다는 의미입니다. 그래서, List<T>를 사용할 수 있는 경우라면 웬만해선 ArrayList의 사용은 피하는 것이 좋습니다. 

 

 물론 List<T>에서 Contain연산을 할 때도 내부적으로 Equal이 들어가게 되면 박싱, 언박싱을 통한 가비지가 발생하지만, 이는 다음 포스팅에서 추가적으로 설명을 해보겠습니다.

 

https://docs.microsoft.com/ko-kr/dotnet/api/system.collections.arraylist?view=net-5.0 

https://www.youtube.com/watch?v=pP9R58fQo_k 

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing

 

 

 

추천글

- C# ValueType과 Reference Type에 대하여 (작성에정)