Search

박싱, 언박싱

Class
메모리
Type
메모리 구조
Created
2023/12/27 04:47
updated
2024/02/07 01:14
날짜

박싱(Boxing)과 언박싱(Unboxing)

박싱(Boxing)과 언박싱(Unboxing)은 C#과 같은 언어에서 값 형식과 참조 형식 간의 변환을 다루는 개념입니다.
이게 무슨 소리냐?
박싱(Boxing)
박싱은 값 형식을 참조 형식으로 변환하는 과정입니다.
C#에서는 모든 값 형식이 System.ValueType을 상속받습니다. 이러한 값 형식(예: int, float, bool 등)을 object 형식이나 인터페이스 타입으로 변환할 때 박싱이 발생합니다.
int num = 123; object obj = num; // 박싱 발생
C#
복사
언박싱(Unboxing):
언박싱은 박싱된 객체를 다시 원래의 값 형식으로 변환하는 과정입니다.
박싱된 객체를 원래의 값 형식으로 변환하려면 명시적 형변환이 필요합니다.
object obj = 123; // 박싱 int num = (int)obj; // 언박싱
C#
복사
실제로 박싱이 자주 발생하는 예제

Action<Object>

게임 개발에서는 다양한 범용 액션 이벤트를 사용하는 경우가 많습니다.
그 이유는 대부분을 포함할 수 있기 때문이죠. 예시는 아래와 같습니다.
public class GameEventHandler { // 이벤트 타입에 따른 Action<object> 딕셔너리 private Dictionary<string, Action<object>> eventHandlers = new Dictionary<string, Action<object>>(); // 이벤트 핸들러 등록 public void RegisterEventHandler(string eventType, Action<object> handler) { if (!eventHandlers.ContainsKey(eventType)) { eventHandlers[eventType] = handler; } else { eventHandlers[eventType] += handler; } } // 이벤트 발생 public void TriggerEvent(string eventType, object eventData) { if (eventHandlers.TryGetValue(eventType, out var handler)) { handler(eventData); } } } // 사용 예시 public class Game { private GameEventHandler eventHandler = new GameEventHandler(); public Game() { // 플레이어 점수 변경 이벤트 핸들러 등록 eventHandler.RegisterEventHandler("PlayerScoreChanged", HandleScoreChanged); } private void HandleScoreChanged(object data) { // 박싱 발생: 'data'는 object 타입이며, 여기서 int로 언박싱 필요 int newScore = (int)data; // 점수 처리 로직... } public void OnPlayerScoreChanged(int score) { // 박싱 발생: int가 object로 변환됩니다. eventHandler.TriggerEvent("PlayerScoreChanged", score); } }
C#
복사
이렇게 범용 이벤트들을 Action<Object>에 담아 이벤트 핸들러를 설계한 경우가 많습니다.
그러나, 이 방법은 박싱과 언박싱을 발생시켜 성능에 영향을 미칠 수 있습니다.
해결 방법
public class GameEventHandler { // 이벤트 식별자와 Action<T>의 결합 private Dictionary<string, Delegate> eventHandlers = new Dictionary<string, Delegate>(); // 이벤트 핸들러 등록 public void RegisterEventHandler<T>(string eventId, Action<T> handler) { if (!eventHandlers.ContainsKey(eventId)) { eventHandlers[eventId] = handler; } else { eventHandlers[eventId] = Delegate.Combine(eventHandlers[eventId], handler); } } // 이벤트 발생 public void TriggerEvent<T>(string eventId, T eventData) { if (eventHandlers.TryGetValue(eventId, out var handler)) { ((Action<T>)handler)(eventData); } } } // 사용 예시 public class Game { private GameEventHandler eventHandler = new GameEventHandler(); public Game() { // 플레이어 점수 변경 이벤트 핸들러 등록 eventHandler.RegisterEventHandler<int>("PlayerScoreChanged", HandleScoreChanged); // 다른 타입의 이벤트 핸들러 등록 예시 eventHandler.RegisterEventHandler<string>("PlayerNameChanged", HandleNameChanged); } private void HandleScoreChanged(int newScore) { // 점수 처리 로직... } private void HandleNameChanged(string newName) { // 이름 처리 로직... } public void OnPlayerScoreChanged(int score) { eventHandler.TriggerEvent<int>("PlayerScoreChanged", score); } public void OnPlayerNameChanged(string name) { eventHandler.TriggerEvent<string>("PlayerNameChanged", name); } }
C#
복사
이런식으로 Action<Object> → Action<T> 제너릭 액션으로 변경하면서 이를 해결할 수 있습니다.

List<Object>, Dictionary<string, Object> 와 같은 컬렉

게임 개발에서 List<object>나 유사한 비제네릭 컬렉션을 사용하며 발생할 수 있는 박싱의 실제 예시를 들어보겠습니다.
public class Inventory { private List<object> items = new List<object>(); public void AddItem(object item) { items.Add(item); } // ... 기타 인벤토리 관리 메서드 ... } public class Game { public void Main() { Inventory playerInventory = new Inventory(); // 박싱 발생 예시 int goldCoins = 100; // 화폐 (값 형식) playerInventory.AddItem(goldCoins); // 박싱 Potion healthPotion = new Potion(); // 소모품 (참조 형식) playerInventory.AddItem(healthPotion); // 박싱 없음 } }
C#
복사

구조체의 인터페이스

이 부분은 자주 발생하지 않지만, 한 번 이해하지 않으면 계속해서 실수를 반복할 수 있습니다.
일반적으로, Dictionary의 키 값을 구조체로 구현할 때는 IEquatable<T>를 사용하여 구현하는 경우가 많습니다.
public struct Point : IEquatable<Point> { public int X, Y; public Point(int x, int y) { X = x; Y = y; } public bool Equals(Point other) { return X == other.X && Y == other.Y; } // GetHashCode는 Dictionary에서 키로 사용될 때 중요합니다. public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); } } // 유저의 유일한 포지션 private Dictionary<Point, string> userPosDict = new Dictionary<Point, string> ();
C#
복사
이 때, Point를 실제 포인트가 아닌 IEquatable로 받을 경우 문제가 생깁니다.
이렇게 명시적 선언이 없어도 박싱이 발생할 수 있는 경우가 있습니다.
Point p = new Point { X = 10, Y = 20 }; // 박싱 발생 IEquatable<Point> equatablePoint = p;
C#
복사
사실 위와 같은 경우는 많이 없지만, var 키워드를 사용하지 않을 경우 이 처럼 문제가 생길 수 있습니다.
안정적인 코드는 다음과 같습니다.
var p = new Point { X = 10, Y = 20 }; // 박싱 발생 var equatablePoint = p;
C#
복사