Boxing과 Unboxing을 설명하기 전에 값타입과 참조타입간의 Casting과정에서 각 변수가 어느 메모리 위치에 저장되는지를 살펴보자.
아래 예제는 값타입인 int형 변수와 참조타입인 object형 변수간의 Casting을 나타낸 예제이다.
class Program
{
public static void Main(string[] args)
{
// 1)
int n1 = 11;
// 2)
object o1 = n1;
// 3)
int n2 = (int)o1;
// 11, 11
Console.WriteLine($"{n1}, {n2}");
}
}
int에서 object로, object에서 int로 Casting되는 부분을 크게 세부분으로 나눌 수 있다.
n1이 할당되는 메모리 위치는 스택영역이며 스택 메모리에 11이 저장된다.
o1은 참조타입, n1은 값타입이기 때문에 o1의 실제 값은 힙영역에 할당되고 스택 메모리에는 힙 메모리 주소가 저장된다. 이 과정에서 n1의 값이 힙 메모리에 복사된다.
o1은 참조타입, n2은 값타입이다. 힙영역에 있는 o1의 값이 n2의 스택 메모리에 복사되어 저장된다.
위의 과정을 그림으로 나타내면 아래 사진과 같다. 여기서 스택에 있는 값이 힙으로 복사되는 과정을 Boxing, 힙에 있는 값이 스택으로 복사되는 과정을 Unboxing이라 한다.

더 나아가서, 언박싱이 일어나기 전에 n1의 값을 1로 바꾸면 서로 다른 메모리 영역이기 때문에 o1에는 영향을 미치지 않는다.
o1이 영향을 받지 않기 때문에 n2의 값도 변하지 않는다.
class Program
{
public static void Main(string[] args)
{
int n1 = 11;
object o1 = n1;
n1 = 1;
int n2 = (int)o1;
// 1, 11
Console.WriteLine($"{n1}, {n2}");
}
}
Boxing과 Unboxing은 값을 복사해오기 때문에 성능저하가 발생할 수 있다. 예를들어, 한번 복사가 발생할 때 크기가 큰 객체를 복사하거나, 크기가 작아도 코드 자체에 잦은 복사가 발생하는 상황이 벌어질 수 있다.
>, <를 통한 비교C#에는 해당 클래스가 크기 비교 기능을 지원한다는 것을 나타내기 위해 IComparable 인터페이스가 존재한다. 직접 정의를 확인하면 매개변수로 object를 받는 함수가 선언되어 있다.

CompareTo 함수는 다른 참조타입의 변수를 입력받도록 미리 정의되어 있는 인터페이스이기 때문에 매개변수가 object로 선언되어 있다.
class Point : IComparable
{
private int x;
private int y;
public Point(int xPos, int yPos)
{
x = xPos;
y = yPos;
}
public int CompareTo(object? other)
{
Point pt = other as Point;
if (x > pt.x) return 1;
else if (x == pt.x) return 0;
return -1;
}
}
class Program
{
public static void Main(string[] args)
{
Point p1 = new Point(1, 1);
Point p2 = new Point(2, 2);
Console.WriteLine(p1.CompareTo(p2));
}
}
Point가 class가 아닌 struct라면 얘기가 달라진다. 매개변수가 object로 선언되어 있기 때문에 실제 넘겨받은 구조체 Point 변수는 참조타입으로 Boxing이 발생하고 이를 다시 Point로 형변환을 하기 때문에 Unboxing이 발생한다.
struct Point : IComparable
{
// ... 중략
public int CompareTo(object? other)
{
Point pt = other as Point;
if (x > pt.x) return 1;
else if (x == pt.x) return 0;
return -1;
}
}
이를 위해 C#에서 값타입을 위해 제네릭 IComparable<T>으로 정의되어 있는 인터페이스가 존재한다. Point가 IComparable<Point>, IComparable을 구현하는 구조체 또는 클래스라면 타입에 상관없이 알맞은 CompareTo를 사용하게 되고 Boxing과 Unboxing이 발생하지 않는다.
class Point : IComparable<Point>, IComparable
{
private int x;
private int y;
public Point(int xPos, int yPos)
{
x = xPos;
y = yPos;
}
public int CompareTo(Point? other)
{
if (x > other.x) return 1;
else if (x == other.x) return 0;
return -1;
}
public int CompareTo(object? other)
{
Point pt = other as Point;
if (x > pt.x) return 1;
else if (x == pt.x) return 0;
return -1;
}
}