내일배움캠프 63일차 TIL
✨ 오늘 내가 배운 것
오늘은 C#에서 값 타입(Value Type)과 참조 타입(Reference Type)을 변환하는 박싱(Boxing)과 언박싱(Unboxing) 개념, 그리고 이 과정에서 발생하는 성능 문제를 해결해주는 제너릭(Generics)에 대해 깊이 있게 배웠다. 게임 개발에서 성능은 정말 중요하니까, 이 개념들을 정확히 아는 것이 필요하다고 생각했다.
📦 박싱(Boxing)이란?
박싱은 **값 타입(int, double, struct 같은 것들)**을 **참조 타입(object 같은 클래스 타입)**으로 변환하는 과정이라고 이해했다.
박싱 과정: 값 타입 변수에 담겨 있던 데이터가 힙(Heap) 메모리에 있는 object 타입으로 복사되는 과정이다. 이 과정에서 값을 복사하는 비용이 발생한다.
int num = 42; // 값 타입인 'num'
object boxed = num; // 박싱: 'num'의 값 42가 힙에 복사되어 'object' 타입으로 변환된다.
위 예시에서 int 타입인 num이 object 타입인 boxed로 변환된다. num의 값 42가 힙 메모리에 복사되고, boxed는 그 힙 메모리의 주소를 참조하게 된다.
박싱의 단점:
성능 저하: 값을 힙에 새로 복사하는 과정이 필요해서 성능에 영향을 줄 수 있다.
불필요한 메모리 사용: 힙에 메모리가 할당되고, 가비지 컬렉션(GC)의 대상이 되기 때문에 추가적인 메모리 오버헤드가 발생할 수 있다.
📦 언박싱(Unboxing)이란?
언박싱은 박싱된 객체를 다시 원래의 값 타입으로 변환하는 과정이다. 박싱의 반대 과정이라고 생각하면 쉽다.
언박싱 과정: object 타입의 참조가 다시 원래의 값 타입으로 복원된다. 이 과정에서는 명시적인 타입 변환이 필수적이다. 만약 원래 타입과 다른 타입으로 언박싱하려고 하면 InvalidCastException이라는 예외가 발생할 수 있다.
예시:
object boxed = 42; // 42가 박싱되어 object 타입으로 저장되어 있다.
int num = (int)boxed; // 언박싱: 'boxed' 객체를 다시 'int'로 변환한다.
위 예시에서 boxed 객체는 object 타입이지만, (int)로 명시적으로 형 변환하여 num이라는 int 타입 변수에 다시 값을 담는 것을 볼 수 있다. 만약 boxed가 int가 아닌 다른 타입(예: string)이었다면, 여기서 InvalidCastException이 발생했을 것이다.
언박싱의 단점:
타입 안전성: 잘못된 타입으로 언박싱을 시도하면 런타임 오류(예외)가 발생할 수 있어서 조심해야 한다.
성능 저하: 언박싱 역시 값을 다시 복원하는 과정에서 성능 저하가 발생할 수 있다.
제너릭(Generics)과 박싱/언박싱
제너릭은 컴파일 시점에 타입을 미리 지정할 수 있게 해주는 강력한 기능이다. 나는 제너릭이 박싱과 언박싱으로 인한 성능 문제와 타입 안전성 문제를 해결하는 데 아주 유용하다는 것을 깨달았다. 제너릭을 사용하면 값을 박싱하지 않고도 다양한 타입을 처리할 수 있다.
제너릭을 사용한 예시:
public class Box<T> // T는 나중에 실제 타입으로 대체될 '타입 매개변수'이다.
{
private T value;
public Box(T value)
{
this.value = value;
}
public T GetValue()
{
return value;
}
}
var intBox = new Box<int>(42); // 'int' 타입으로 'Box'를 생성했다. 박싱이 발생하지 않는다.
int value = intBox.GetValue(); // 'int' 타입으로 바로 접근한다. 언박싱도 발생하지 않는다.
위 예시에서 Box<T> 클래스는 제너릭을 사용해서 T 타입의 데이터를 저장한다. Box<int>로 만들면 int만 저장할 수 있는 Box가 되고, Box<string>으로 만들면 string만 저장할 수 있는 Box가 된다. 이렇게 하면 int, string, double 등 다양한 타입을 박싱/언박싱 없이 처리할 수 있다.
제너릭을 사용하면 타입 안전성이 컴파일 시점에 보장되기 때문에 런타임 예외를 피할 수 있고, 불필요한 박싱/언박싱을 제거하여 성능을 최적화할 수 있다.
🧐 박싱과 언박싱을 사용하는 경우
박싱과 언박싱은 다양한 타입을 다룰 때 필요하지만, 성능에 민감한 상황에서는 제너릭을 사용하는 것이 훨씬 좋다는 것을 배웠다.
박싱을 사용하는 예시
object 배열이나 ArrayList 같은 비제너릭 컬렉션에 여러 타입의 값을 저장하고자 할 때 박싱이 암묵적으로 발생한다.
제너릭을 사용하는 예시
List<T>, Dictionary<TKey, TValue>와 같은 제너릭 컬렉션 클래스들은 타입 안전성을 제공하고, 박싱과 언박싱을 피할 수 있어서 성능이 중요한 경우에 주로 사용한다.
✍️ 결론 및 느낀 점
박싱과 언박싱은 값 타입과 참조 타입 간의 변환 과정이며, 이 과정은 힙 할당 및 복사로 인해 성능에 영향을 줄 수 있다는 것을 명확히 이해했다.
제너릭은 컴파일 시점에 타입을 지정하여 타입 안전성을 보장하고, 불필요한 박싱과 언박싱을 피할 수 있기 때문에 성능을 최적화하는 데 필수적인 기능이라는 것을 깨달았다.
앞으로는 성능이 중요한 코드에서는 항상 제너릭을 우선적으로 고려하고, 박싱/언박싱이 발생하는 지점을 인지하며 코드를 작성해야겠다고 다짐했다.

'TIL' 카테고리의 다른 글
| A* 알고리즘 (1) | 2025.07.10 |
|---|---|
| Unity에서 Find() 함수 사용을 자제해야 하는 이유 (2) | 2025.07.09 |
| 셀룰러 오토마타로 육각형 지형 만드는 시스템 구현 (0) | 2025.07.07 |
| 육각형 맵 Flat-Top 전환 & 6방향 랜덤 지형 배치 (0) | 2025.07.04 |
| 아이템 레시피 데이터를 SO에 반영하고 CSV ↔ SO 동기화 기능 구현 (2) | 2025.07.03 |