박싱(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#
복사