잘못된 개념
- 흔히들 Boxing은 ‘object로 형변환하는 작업’으로 착각하고 있다. 마찬가지로 UnBoxing 또한 ‘object에서 다른 타입으로 형변환 하는 작업’으로 착각하고 있다.
- 굳이 object가 아니더라도 아래와 같은 코드도 ValueType이 Reference Type으로 변환되므로 Boxing이다.
int a = 123;
string b = a.ToString();
ArrayList collection = new ArrayList();
for (int i = 99; i > 0; i--)
{
collection.Add(i);
}
- 첫번째 예제코드는 object로 형변환 하지 않았어도 Boxing이다.
- System.ValueType은 Thread Stack에 할당될 수도 있고, Managed Heap에 할당될 수도 있는 타입이다. Thread Stack에만 할당된다는 편견은 버려라.
.NET의 Boxing
- .NET의 포인터 변수 크기는 x86에서 4Byte, x64에서 8Byte 다.
- .NET의 타입 객체 포인터(Type Object Pointer) 또는 메소드 테이블 포인터(Method Table Pointer) 변수의 크기는 x86에서 4Byte, x64에서 8Byte 다.
- .NET의 동기화 블록 인덱스(Sync Block Index) 의 크기는 x86에서 4Byte, x64에서 8Byte 다.
- Boxing이란, System.ValueType을 상속받는 struct 타입의 값이 Thread Stack 영역에 할당되고 이 값을 Managed Heap 영역으로 복사(또는 새로 할당) 하는 것을 의미한다. 복사라는 것에 굉장히 많은 의미가 담겨있다. Stack영역에 값을 바로 할당하면 정말 값만 할당된다. 이 값을 Heap으로 복사할 때는 아래와 같은 정보가 추가적으로 필요하다.
- Managed Heap을 참조할 수 있는 Thread Stack의 포인터변수
- Managed Heap의 실제 인스턴스 공간
- 타입 객체 포인터
- 동기화 블록 인덱스
- 동기화 블록 인덱스는 Thread Stack의 포인터 변수가 참조하는 실제 주소의 시작부분에서 - 방향으로 4Byte, 8Byte 방향으로 생성된다.
- 위에서 설명한대로 Boxing이란 object로 타입캐스팅한다는 의미가 아닌, 위의 인스턴스를 새로 할당한다는 개념 이다.
- 즉, stack에서 int형 변수를 사용하다가, object로 형변환을 하면, x86 기준으로 4Byte가 16Byte(Thread Stack 포인터 변수 4Byte + 타입 객체 포인터 4Byte + Managed Heap에 할당된 실제 인스턴스 크기 4Byte + 동기화 블록 인덱스 4Byte)가 되는 마법을 볼 수 있다.
- 또한 Boxing된 변수는 Thread Stack의 변수처럼 메소드가 종료되었다고 바로 사라지는 것이 아닌, Garbage Collector가 수집해야만 제거가 가능하므로 바로 제거되지도 않는다.
- Boxing을 하지말고 차라리 처음부터 Managed Heap에 할당해놓고 사용하는게 빠르고 효율적이며, Garbage가 덜 생성되는 방법이다.
.NET의 UnBoxing
- UnBoxing은 Boxing보다 단순한 작업이다. 포인터 변수가 사라지고, 동기화 블록 인덱스가 사라지는 작업이므로 상대적으로 간편하다. 하지만 이 역시, Managed Heap에서 Thread Stack으로 복사(또는 새로 할당) 하는 작업이므로 메모리 낭비가 맞다.
.NET의 Generic
- 컬랙션 객체에 원래 ValueType의 값을 저장하고 싶다면 위의 예제처럼 ArrayList를 통해 Boxing이 되는 형태로 사용했지만, .NET Framework 2.0에서 Generic이 도입되면서 Boxing을 예방할 수 있게되었다.
- Generic은 런타임 시점에서 인스턴스를 형성할 때 Generic Type으로 지정되어 인스턴스를 할당하기 때문에 Boxing이 아닌 처음부터 Reference type의 ValueType을 할당할 수 있다. 아래와 같은 구조라고 보면 된다.
class Sample
{
int a { get; set; }
}
- 위의 int는 ValueType이지만 Class의 Property로 정의 되었으므로 Reference Type이다. Generic이 위와 같이 Reference Type의 ValueType을 사용할 수 있게한다.
- List는 위와 같이 Reference Type으로 동작하므로 Boxing이 없고, 메소드 같은 경우 아래 코드를 보면 generic이 int형태로만 동작하기 때문에 boxing이 없다. 따라서 Boxing이 예방된다.
public T Sum<T>(T t1, T t2) where T : struct
{
return t1 + t2;
}
var result = Sum<int>(3, 4);
Console.WriteLine(result); // 7
Dictionary
참고자료