Boxing & Unboxing

값 형식 vs. 참조 형식

  값 형식 참조 형식
저장 위치 stack heap
데이터 입력한 데이터 값 데이터 참조
특징 * 스택 프레임이 종료되면 사라짐
* shallow copy
* GC에 의해 처리
* deep copy
변환 값 형식 -> 참조 형식(Boxing) 참조 형식->값 형식(Unboxing)
타입 예시 bool, char, byte, decimal, double,
enum, float, int long, short, sbyte,
struct, uint, ulong, ushort
class, interface, delegate, object, string, dynamic

Boxing

// Boxing은 implicit
int i = 10;
object o = i; //performs boxing

// ArrayList의 element들은 모두 reference type이다
ArrayList list = new ArrayList();
list.Add(10); // boxing
list.Add("Bill");
  • 값 형식 -> 참조 형식
  • Stack의 데이터를 Heap으로 복사
  • object 타입으로 변환해 복합자료형의 Container에 사용 가능
  • 완전히 새로운 개체를 만들며, 할당 작업보다 최대 20배의 시간 소요

Named Boxing

post_thumbnail

  • Stack에 할당된 Value Type i의 값을 Heap으로 복사해온다
  • Wrapping된 i의 값은 Heap에 새롭게 System.Object를 생성하고 주소가 생긴다
  • Stack에 Reference Type o는 이 주소를 참조한다
    • boxed된 i는 Heap에 존재하는 주소의 객체이다
    • 이 일련의 과정은 CLR에 의해 수행된다
int i = 10;
object o = i; // boxing
o = 20;
Console.WriteLine(i); // output: 10
  • Stack의 i와 Heap의 boxed i는 별개의 객체이다
  • 복사된 변수의 값을 바꾸더라도 원본에 그 변경이 적용되지 않는다

Unboxing

post_thumbnail

  • 참조 형식 -> 값 형식
  • Heap의 데이터를 Stack으로 복사
  • 캐스팅 과정에서 할당 작업보다 4배의 시간이 걸림
// Boxed 오브젝트를 바로 캐스팅하는 것은 불가능
int i = 10;
object o = i; // boxing
double d = (double)o; // runtime exception

// Unboxing을 한 뒤 캐스팅해야 함
int i = 10;
object o = i; // boxing
double d = (double)(int)o; // valid

Boxing vs. Unboxing

class Test  
{  
    static void Main()  
    {  
        int i = 1;  
        object o = i; // boxing  
        int j = (int)o; // unboxing  
    }  
}  
  • Boxing과 Unboxing은 C#이 허용하는 변환
  • 값 타입에서 참조 타입 또는 그 반대로 변환 가능

Boxing은 Garbage를 생성하는가?

  • 그렇다
  • Boxing할 때마다, 혹은 새로 생성된 object들은 종국에 garbage가 된다
    • 물론 매우 드물게, app이 종료될 때까지 참조하고 있는 경우도 있다.

Boxing과 Unboxing 방지하는 법?

  • Generic 클래스와 메서드, 컬렉션을 사용할 것 .Net 2.0 이전의 컬렉션은 System.Object 타입의 객체를 추가하려는 경우 자동으로 박싱 수행
  • Value 타입을 System.Object나 인터페이스 타입으로 변경하는 코드를 작성하지 않을 것
Console.WriteLine($"A few numbers:{firstNumber}, {secondNumber}, {thirdNumber}");
  • 위의 코드에서 firstNumber, secondNumber, thirdNumber가 int 타입이라고 가정하자
  • 보간 문자열에 대입되면서 Sysmte.Object 타입으로 변환된다
    • 즉, Boxing이 발생한다
  • 그런 다음, string 타입으로 변환되기 위해 ToString 함수를 호출한다
Console.WriteLine($"A few numbers:{firstNumber.ToString()}, 
             {secondNumber.ToString()}, {thirdNumber.ToString()}");
  • 차라리 string instance를 직접 전달하라
  • 여기에서는 C# 10.0 이후로는 보간 문자열이 더 빠르다고 한다
    • C# 10.0 부터는 DefaultInterpolatedStringHandler 형식이 추가되어, StringBuilder 형식으로 문자열이 생성된다.(https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/)

How to prevent boxing & unboxing:

  1. int, double, float 등과 같은 타입에 대해 ToString 메서드 사용
  2. 값 타입 Array나 List에 대해 for loop를 사용하라
    • foreach loop 나 LINQ queries를 사용하지 마라
  3. string 문자열에 대해 for loop를 사용하라
    • foreach loop 나 LINQ queries를 사용하지 마라
  4. 꼭 필요한 상황이 아니라면 object 타입에 값 타입을 할당하지 마라
  5. ArrList & HashTable 등의 Non-generic 컬렉션보다는 List<>, Dictionary<> 과 같은 generic 타입의 컬렉션을 사용하라
  6. int?, float? 등과 같은 Nullable<> 타입을 사용하라
  7. string.Format (SrtingBuilder.AppendFormat)이나 다른 ‘params object[]’를 인자로 하는 API들을 사용할 때, ToString() 메서드를 사용해 값 타입으로 넘겨라
  • 원문의 4번은 이해가 안돼서 일단 제외…

출처