[Unity] 나는 Boxing이 뭔지 알고 있을까
![[Unity] 나는 Boxing이 뭔지 알고 있을까](/_next/image?url=https%3A%2F%2Fdata.develog.develrocket.com%2Fupload%2Fdevelog%2Fgmumdev%2F1790954185360-cyx8ex%2F_____.png&w=3840&q=75)
정의
Boxing is the process of converting a value type to the type
objector to any interface type implemented by this value type. When the common language runtime (CLR) boxes a value type, it wraps the value inside a System.Object instance and stores it on the managed heap.박싱은 값 타입을
object타입 또는 그 값 타입이 구현한 인터페이스 타입으로 변환하는 과정이다. CLR은 값을 System.Object 인스턴스로 감싸 managed heap에 저장한다
- Microsoft learn <링크>
1. 클래스의 필드로 구조체
using UnityEngine;
[System.Serializable]
public struct EnemyData
{
public int id;
public int biome;
public EnemyData(int id, int biome)
{
this.id = id;
this.biome = biome;
}
}
public class Enemy : MonoBehaviour
{
[SerializeField] private EnemyData data;
}
- EnemyData data는 박싱되지 않았다.
- EnemyData data는 힙에 있다.
2. 딕셔너리의 키로 구조체
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public struct EnemyData
{
public int id;
public int biome;
public EnemyData(int id, int biome)
{
this.id = id;
this.biome = biome;
}
}
public class EnemyManager : MonoBehaviour
{
private readonly Dictionary<EnemyData, int> enemies = new(); // 뭔가 들어있다고 가정
public bool IsExist(int id, int biome)
{
return enemies.TryGetValue(new EnemyData(id, biome), out var cnt) && cnt > 0;
}
}
- 조회시 ValueType.GetHashCode 호출돼서 박싱.
- 동일 해시값끼리 비교할 때 Equals(object)에서 박싱.
- EnemyData가 IEquatable 를 구현하고 GetHashCode()를 override 하면 두 박싱을 막을 수 있다.
3. 인터페이스로 받은 구조체
public struct EnemyData : IData { /* ... */ }
public static void SomeMethod(IData d) // SomeMethod(new EnemyData(...))
{
//...
}
- EnemyData -> IData 변환에서 박싱된다.
4. 제네릭 제약 조건으로 받은 구조체
public struct EnemyData : IData { /* ... */ }
public static void SomeMethod<T>(T data) where T : IData // SomeMethod(new EnemyData(...))
{
data.SomeInterfaceMethod();
}
- 박싱 없다. 다음 포스트에서 자세히 다룰 예정.
5. Debug.Log(값타입)
- 박싱 된다. Debug.Log(object message)이므로 값 타입 인수가 object로 박싱된다.
6. 값 타입의 람다 캡처(클로저)
- 박싱 없다.
- 대신 DisplayClass 인스턴스가 생길 때 할당은 있다.
- DisplayClass 인스턴스는 캡처된 변수가 선언된 스코프에 진입할 때 생성된다.
- 캡처된 값 타입은 DisplayClass의 필드다.
7. 코루틴 안에 값타입 변수 선언
private IEnumerator FadeOut(float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
//...
yield return null;
}
gameObject.SetActive(false);
}
- FadeOut을 호출하는 순간 힙 할당이 발생한다.
- 정확히는 상태머신 클래스의 인스턴스가 생긴다.
- StartCoroutine(FadeOut(0.3f))도 결국 FadeOut(0.3f)가 먼저 호출되므로 마찬가지.
- elapsed와 duration은 Stack에 없다. Managed Heap에 있다. 그러나 박싱은 아니다.
- 위의 상태머신 클래스 안에 필드로 elapsed와 duration이 있다.
- gameObject.SetActive(false) 때문에 this 참조도 필드로 들어간다. this.gameObject로 접근해야하기 때문이다.
정리
# | 상황 | 박싱 | 힙 할당 | 할당되는 것 |
|---|---|---|---|---|
1 | 클래스의 필드인 구조체 | X | X (추가 할당 없음) | 없음. |
2 |
| O | O | 박스 (조회마다) |
3 | 인터페이스 타입 매개변수로 전달 | O | O | 박스 (호출마다) |
4 | 제네릭 제약 조건 매개변수로 전달 | X | X | 없음 |
5 |
| O | O | 박스 (호출마다) |
6 | 람다의 값 타입 캡처 | X | O | DisplayClass 인스턴스, delegate 인스턴스 |
7 | 코루틴 안의 값 타입 지역 변수 | X | O | 상태 머신 인스턴스 ( |

![[Unity] PC 세이브 데이터 Android로 옮기기](https://data.develog.develrocket.com/upload/develog/muchan/1792206352286-hd4xz4/_____2026-10-17_114953.png)
![[Unity] 제네릭 박싱 예방](https://data.develog.develrocket.com/upload/develog/gmumdev/1792123312021-bhxx2i/_____.png)
