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
- 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
- 참조 형식 -> 값 형식
- 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:
- int, double, float 등과 같은 타입에 대해 ToString 메서드 사용
- 값 타입 Array나 List에 대해 for loop를 사용하라
- foreach loop 나 LINQ queries를 사용하지 마라
- string 문자열에 대해 for loop를 사용하라
- foreach loop 나 LINQ queries를 사용하지 마라
- 꼭 필요한 상황이 아니라면 object 타입에 값 타입을 할당하지 마라
- ArrList & HashTable 등의 Non-generic 컬렉션보다는 List<>, Dictionary<> 과 같은 generic 타입의 컬렉션을 사용하라
- int?, float? 등과 같은 Nullable<> 타입을 사용하라
- string.Format (SrtingBuilder.AppendFormat)이나 다른 ‘params object[]’를 인자로 하는 API들을 사용할 때, ToString() 메서드를 사용해 값 타입으로 넘겨라
- 원문의 4번은 이해가 안돼서 일단 제외…
출처
- https://cho22.tistory.com/56
- https://www.c-sharpcorner.com/article/boxing-and-unboxing-in-C-Sharp/
- https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/reference-types
- https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/builtin-types/value-types
- https://velog.io/@cgotjh/C-%EB%B0%95%EC%8B%B1Boxing%EA%B3%BC-%EC%96%B8%EB%B0%95%EC%8B%B1Unboxing
- https://www.tutorialsteacher.com/articles/boxing-unboxing-in-csharp
- https://stackoverflow.com/questions/2227334/does-boxing-create-garbage-in-net
- https://afsdzvcx123.tistory.com/entry/Effective-C-Item-9-%EB%B0%95%EC%8B%B1%EA%B3%BC-%EC%96%B8%EB%B0%95%EC%8B%B1%EC%9D%84-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EB%9D%BC
- https://blog.uniony.me/effective-cs-item4/
- http://vaibhavgawali.net/avoid-boxing-unboxing-improve-performance/