본문 바로가기
TIL

박싱, 언박싱 그리고 제너릭

by vvin39 2025. 7. 8.

내일배움캠프 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>와 같은 제너릭 컬렉션 클래스들은 타입 안전성을 제공하고, 박싱과 언박싱을 피할 수 있어서 성능이 중요한 경우에 주로 사용한다.

 

✍️ 결론 및 느낀 점

박싱과 언박싱은 값 타입과 참조 타입 간의 변환 과정이며, 이 과정은 힙 할당 및 복사로 인해 성능에 영향을 줄 수 있다는 것을 명확히 이해했다.

제너릭은 컴파일 시점에 타입을 지정하여 타입 안전성을 보장하고, 불필요한 박싱과 언박싱을 피할 수 있기 때문에 성능을 최적화하는 데 필수적인 기능이라는 것을 깨달았다.

앞으로는 성능이 중요한 코드에서는 항상 제너릭을 우선적으로 고려하고, 박싱/언박싱이 발생하는 지점을 인지하며 코드를 작성해야겠다고 다짐했다.

 

 

 

최근댓글

최근글

skin by © 2024 ttuttak