박싱과 언박싱이 무엇일까?
공식 문서에는 다음과 같이 설명되어 있다.
Boxing은 값형식을 object 형식 또는 이 값 형식에서 구현된 임의의 인터페이스 형식으로 변환하는 프로세스입니다. CLR(공용 언어 런타임)은 값 형식을 boxing할 때 값을 System.Object 인스턴스 내부에 래핑하고 관리되는 힙에 저장합니다. unboxing하면 개체에서 값 형식이 추출됩니다. Boxing은 암시적이며 unboxing은 명시적입니다. Boxing 및 unboxing의 개념은 개체로 처리할 수 있는 모든 값 형식에서 형식 시스템의 C#에 통합된 뷰의 기반이 됩니다. |
이게 무슨 말인지 하나씩 알아보면 다음과 같다.
1. Boxing은 값형식을 object 형식 또는 이 값 형식에서 구현된 임의의 인터페이스 형식으로 변환하는 프로세스... |
값 타입(int, float, long, struct...)이 object형식(모든 타입의 최상위 형식)으로 변환되는 과정을 박싱이라고 하고 반대로 값 타입으로 되돌아 오는 과정을 언박싱이라고 한다. 참조 타입이 object로 변환되는 것은 박싱이 아니다. 그냥 object형식으로의 업캐스팅이다. |
2. CLR(공용 언어 런타임)은 값 형식을 boxing할 때 값을 System.Object 인스턴스 내부에 래핑하고 관리되는 힙에 저장... |
박싱과 언박싱을 하는 주체는 CLR(소스코드를 .Net에서 동작하도록 해주는 가상머신)이다. 박싱 혹은 언박싱이 되는 타입은 값 타입인데, 이 값이 Object형식으로 인스턴스화 되어 저장되어 스택이 아닌 힙에 저장된다는 의미이다. |
3. Boxing은 암시적이며 unboxing은 명시적... |
암시적이라는것은 형변환이 암시적, 다시말해 암시적 형변환인 업캐스팅에 해당하고 형변환이 명시적이라는것은 다운캐스팅에 해당한다. 이는 object형식이 최상위 클래스이기 때문에 생기는 당연한 결과이다. |
4. Boxing 및 unboxing의 개념은 개체로 처리할 수 있는 모든 값 형식에서 형식 시스템의 C#에... |
object는 모든 타입의 최상위 형태(root class)이므로 이를 통해 서로 다른 형식도 object형식으로 일괄적인 처리가 가능하다. |
정리하자면 값 형식을 오브젝트 클래스 형식으로 변환할 때 박싱이, 오브젝트 클래스에서 값 형식으로 다시 변환할 때 언박싱이 이루어지며 박싱은 암시적 형 변환, 언박싱은 명시적 형 변환을 해야 한다.
어떠한 방식으로 박싱과 언박싱이 이루어지는가?
값 타입을 오브젝트 형식으로 변환하여 박싱이 이루어지면 힙 영역에 object형식의 인스턴스가 할당되고 해당 값이 복사된다.
반대로 언박싱이 이루어지면 힙 영역에 할당된 object형식을 찾아서 인스턴스의 값을 형식 변수에 복사 붙여 넣기 한다.
float f = 0.1f;
object o = f;
f = 0.2f;
Debug.Log(f);
Debug.Log(o);
위 코드를 실행하면 순서대로 0.2f와 0.1f라는 결과를 얻는다. 왜냐면 박싱할 때 f의 값을 복사해서 o가 갖고 있기 때문이다. 따라서 f가 변경된다 하더라도 o는 기존의 값만을 갖고 있을 뿐이다.
어떻게 사용할 수 있을까?
아래의 스크립트 예시를 통해 확인하자.
public class BoxingAndUnBoxing : MonoBehaviour
{
public List<object> myArrayList = new List<object>();
//오브젝트 형식의 리스트 선언
void Start()
{
myArrayList.Add(5); // 정수형
myArrayList.Add("hello"); // 문자열형식
myArrayList.Add(new MyStruct()); // 구조체 형식
myArrayList.Add(1.2f); // 실수형
myArrayList.Add(new int[3]{1,2,3}); //정수형 배열 형식
// 오브젝트 리스트에 값 넣기
Debug.Log(myArrayList[1]);
Debug.Log(((System.Int32[])myArrayList[4])[0]);
// 오브젝트 리스트에서 값 꺼내오기
Debug.Log(myArrayList[1].GetType());
Debug.Log(((System.Int32[])myArrayList[4])[0].GetType());
// 각 값들의 타입 확인
}
}
public struct MyStruct
{
//something...
}
로그를 찍었을 때 결과는 다음과 같다.
myArrayList에 들어간 타입은 모두가 서로 다르지만 첫 번째와 네 번째 값을 꺼냈을 때 모두 올바르게 저장되었다가 꺼내지는 것을 확인할 수 있다. 이것은 리스트뿐만 아니라 object [] 형태의 배열로 만들었을 때에도 똑같이 동작한다. 다시 말해서 어떤 타입이던지 object클래스로 박싱이 가능하고 이를 이용해 배열이나 리스트로 한 번에 관리가 가능하다는 것이다.
3번째와 4번째 로그는 해당 값들의 타입을 출력한 것이다. 확인해 보면 처음 넣어주었던 string과 Int32(배열의 첫 번째 요소)의 형식이 정확히 출력되고 있다. 로그를 찍으면서 오브젝트 형식이었던 것이 다시 언박싱되어 원래 형식으로 돌아오게 된 것이다.
유의해야 할 점
공식문서에 따르면 박싱을 진행하면 참조 할당 때보다 약 20배나 더 오래 걸린다고 한다(언박싱은 4배). 박싱과 언박싱은 컬렉션(Collection) 클래스에서도 발생하게 되는데, 컬렉션의 클래스는 모든 타입의 값들을 다 박싱 해서 저장하고 언박싱해서 불러오기 때문이다. 그러나 항상 박싱과 언박싱이 발생하는 것은 아니다. "값 형식을 object 형식 또는 이 값 형식에서 구현된 임의의 인터페이스 형식으로 변환" 에서 알 수 있듯이 값 형식이 변환될 때 박싱이 이루어 지므로 값 타입이 ArrayList 같은 클래스에 담기면 실행 속도의 문제를 일으킬 수 있다.
따라서 박싱이 일어날 경우 일반화가 가능한 제네릭<T> 사용을 권장하고 있다. 제네릭을 사용하면 값 타입이더라도 박싱과 언박싱이 발생하지 않기 때문이다.
'C# 프로그래밍 > 문법 개념' 카테고리의 다른 글
[C#] 널 허용 값 형식 (Nullable) (0) | 2022.05.30 |
---|---|
[C#] 오브젝트 (Object) (0) | 2022.05.27 |
[C#] 튜플과 딕셔너리 (Tuple & Dictionary) (0) | 2022.05.26 |
[C#] 패턴 일치 (Pattern matching) (0) | 2022.05.26 |
[C#] 추상화 (Abstraction) (0) | 2022.05.26 |