나만의 작은 도서관

[C#] 문법 정리 #7. 값형과 참조형, 박싱과 언박싱 본문

Unity/문법 및 메소드(System)

[C#] 문법 정리 #7. 값형과 참조형, 박싱과 언박싱

pledge24 2024. 11. 22. 20:10

값형과 참조형

대부분의 프로그래밍 언어에서 그렇듯, C#에서도 변수에 데이터를 저장하는 두 방식, 값형과 참조형이 있다.

 

값형(Value Type)

값형은 변수에 값을 직접 저장한다. 변수가 실제 데이터를 보유하고 있으며, 해당 변수를 다른 변수에 할당하거나 전달할 때는 값이 복사된다. 값형은 변수 내 데이터를 수정해도 다른 변수에 영향을 주지 않는다. 기본 데이터 타입(int, float, double 등)들이 값형에 해당한다.

 

참조형(Reference Type)

참조형은 변수가 데이터의 메모리 주소를 참조하여 저장한다. 변수가 실제 데이터를 가리키는 참조를 갖고 있으며, 다른 변수에 대입 시 참조값(메모리 주소)가 복사된다. 참조형은 변수 내 데이터를 수정하면 다른 변수에도 영향을 준다. 클래스, 배열, 인터페이스 등이 참조형에 해당한다.


박싱과 언박싱

박싱(Boxing)은 값형 -> 참조형, 언박싱(Unboxing)은 참조형 -> 값형으로 변환하는 과정을 의미한다.

 

박싱(Boxing)

박싱

박싱은 값형 변수의 데이터를 object 타입에 대입하는 것으로 실행된다. 대입 시 데이터를 힙 영역에 저장하고 object 타입인 참조 변수가 해당 데이터를 참조한다. 

+) 참고로, 박싱된 객체와 원래의 값형 변수는 서로 독립적이므로 값을 수정하더라도 상호간에 영향을 주지 않는다.

int number = 42;
object boxedNumber = number; // 박싱

 

 

언박싱( Unboxing )

언박싱

 

언박싱은 박싱된 object 타입에서 값을 추출하여 값형 변수에 할당하는 것으로 실행된다. 주의할 점은 명시적으로 형변환을 해야 하며, 잘못된 형식으로 언박싱하면 런타임 에러(InvalidCastException)가 발생할 수 있다. 언박싱은 런타임에서 타입 검사가 이루어진다.

object obj = 42;
float value = (float)obj; // 언박싱 시도. 런타임 오류 발생

 

 

박싱, 언박싱을 하는 이유

타입 클래스 구조

 

첫번째 이유는 서로 다른 타입의 여러 데이터를 하나의 최상위 타입인 Object로 통일시켜 하나의 컬렉션이나 메서드에서 다룰 수 있는 유연성을 얻을 수 있기 때문이다.

두번째 이유는 제너릭이 존재하기 이전, 비제너릭 컬렉션(ex. ArrayList)에 저장할 데이터는 반드시 참조 형식이어야 했다. 따라서, 참조 형식이 아닌 기본 타입들을 컬렉션에 저장하려면 박싱이 필수적이었다.

 

리스트를 이용한 박싱, 언박싱 활용 예제

List<object> myList = new List<object>();

myList.Add(10);        // int -> object (박싱 발생)
myList.Add(3.14f);     // float -> object (박싱 발생)
myList.Add("Hello");   // string은 참조 형식이므로 박싱 없음

int value1 = (int)myList[0]; 
float value2 = (float)myList[1]; 
string value3 = (string)myList[2];

 

성능 저하의 문제점

박싱과 언박싱을 하게되면 성능이 크게 저하되는 문제가 있다. 박싱은 스택에 있던 값형 데이터를 힙 메모리에 복사해오는 과정에서 오버헤드가 발생하며 힙 메모리 추가 사용으로 인한 관리 비용 증가시킨다. 언박싱은 박싱과 반대로 힙 메모리에 있던 데이터를 다시 스택 메모리로 복사해오는 과정에서 오버헤드가 발생하게된다. 요약하자면 다음과 같다.

  • 박싱: 힙 메모리 할당과 데이터 복사로 인해 성능 저하.
  • 언박싱: 데이터 복사와 형식 검증으로 인해 성능 저하.

이러한 성능 저하로 인해 단순히 힙에 넣는 작업에 비해 박싱을 통할 시 20배, 언박싱 시 4배정도의 시간이 소요된다고 한다. (출처)

 

다행히도 C# 2.0 이후, 제너릭(generic)과 같은 기능이 도입되면서 박싱과 언박싱을 피할 수 있는 방법이 제공되어 성능 저하의 문제는 어느정도 해결되었다. 그럼에도 불구하고, 일부 상황에서는 제너릭이 박싱 언박싱을 대체하지 못하는 경우가 종종 있는데, 이에 대해 좀 더 알고 싶다면 해당 링크를 참고하면 좋다.

 

결론

박싱 언박싱은 서로 다른 타입의 데이터를 하나의 컬렉션에 저장할 수 있다는 유연성을 제공하지만, 성능을 떨어뜨리기 때문에 사용을 최대한 자제해야한다.