[C#] object, Boxing/Unboxing

심지섭·2025년 3월 12일
0

C#

목록 보기
1/2

object 자료형

C#에는 int, long, char 등 다양한 자료형이 있다. 이 중 object는 모든 데이터를 다룰 수 있는 최상위 자료형이다. C#에서 모든 자료형은 object를 상속하므로, object는 모든 자료형의 부모라고 볼 수 있다.

object _float = 1.23f;
object _int = 1;
object _string = "Hello"; // string은 원래 참조 타입이므로 Boxing 아님
object _bool = true;

박싱(Boxing)과 언박싱(Unboxing)

object는 참조 타입이다. 따라서 object 변수에 값 타입을 할당하면 스택(stack)에는 주소값이 저장되고, 힙(heap)에는 실제 데이터가 저장된다. 이렇게 값 타입을 참조 타입으로 변환하는 과정을 박싱(Boxing)이라고 한다.

object _object = 1; // Boxing
int _int = (int)_object; // Unboxing

반대로 힙에 있는 데이터를 다시 스택으로 가져오는 과정을 언박싱(Unboxing)이라고 한다. 즉, 원래 주소값을 담고 있던 자리에 데이터를 직접 복사하는 것이다. 이때 힙에 남아있는 데이터는 가비지가 되고 GC가 해제할 때까지 기다려야 한다.

따라서 박싱/언박싱은 힙 메모리 할당과 데이터 복사 비용이 크기 때문에 성능상 문제가 있다. 이러한 문제로 object는 잘 쓰이지 않는다고 한다.

그렇다면 object는 어디에 쓰일까?

object는 모든 자료형의 조상이기 때문에 다형성(Polymorphism)을 지원한다. 즉, 모든 타입을 object로 다룰 수 있기 때문에 다양한 상황에서 활용될 여지가 있다.

예를들어,

public abstract class ObjectPolymorphismExample
{
	// 추상화는 하고 싶은데 필드 타입이 명확하지 않은 경우 (ex. 퀘스트 시스템)
    public abstract object value { get; }

    ...중략...
}

이처럼 object 타입을 사용하면 다양한 자료형의 값을 하나의 부모 타입으로 다룰 수 있어, 여러 상황에서 유용하게 활용될 수 있다.

물론 제네릭(Generic)을 사용하면 타입이 컴파일 타임에 고정되므로 형변환이 필요 없고, 안전성을 확보할 수 있다. 또한 박싱/언박싱 문제도 피할 수 있다. 따라서 C# 2.0 버전 이상이라면 제네릭을 사용하는 것이 성능과 안전성 측면에서 더 좋은 선택이긴 하다.

그러나 애초에 힙에 할당된 참조 타입 변수들은 박싱/언박싱이 일어나지 않기도 하고, 값 타입을 박싱/언박싱 하더라도 매 프레임마다 일어나는 것이 아니라면 성능에 미치는 영향은 미미하다. 무엇보다 제네릭을 사용하면 이를 담는 컬렉션(배열, 리스트) 등을 만든다고 했을 때, 타입이 달라지면

public abstract class ObjectPolymorphismExample<T>
{
    public abstract T value { get; }

    ...중략...
}

System.Object의 주요 메서드

C#에서 모든 자료형은 System.Object 클래스를 상속하며, 아래와 같은 기본 메서드를 사용할 수 있다.

namespace System
{
    public class Object
    {
        public Object();

        ~Object();

        public static bool Equals(Object objA, Object objB);
        public static bool ReferenceEquals(Object objA, Object objB);
        public virtual bool Equals(Object obj);
        public virtual int GetHashCode();
        public Type GetType();
        public virtual string ToString();
        protected Object MemberwiseClone();
    }
}

public static bool Equals(Object objA, Object objB);

두 객체(objA와 objB)가 동일한지 비교하는 정적 메서드

기본적으로 객체의 참조를 비교하지만, 오버라이드하여 값 비교를 할 수 있다.

public static bool ReferenceEquals(Object objA, Object objB);

두 객체가 같은 참조를 가리키는지 확인하는 정적 메서드

이 메서드는 객체의 값이 아닌 참조 자체를 비교한다. 즉 두 객체가 모두 동일한 메모리 위치를 가리키는지를 확인한다.
추가적으로 둘 다 null이면 true를 반환한다.

public virtual bool Equals(Object obj);

두 객체가 동일한지 비교하는 인스턴스 메서드

기본적으로 객체의 참조를 비교하지만, 오버라이드하여 값 비교를 할 수 있다.

public virtual int GetHashCode();

객체의 해시 코드를 반환하는 메서드

해시 코드는 객체를 비교하는 데 사용되는 값으로, 기본적으로 참조에 기반한 해시 값을 반환한다. 이 메서드도 오버라이드하여 객체의 값에 기반한 해시 코드를 구현할 수 있다.

public Type GetType();

객체의 런타임 타입을 반환하는 메서드

객체가 어떤 타입인지 알 수 있다.

public virtual string ToString();

객체의 문자열 표현을 반환하는 메서드

기본적으로 클래스 이름을 반환하지만, 오버라이드해서 객체의 구체적인 데이터를 문자열로 반환할 수 있다.

protected Object MemberwiseClone();

객체의 얕은 복사본을 생성하는 메서드

객체의 필드 값을 복사하지만, 참조형 필드는 복사하지 않고 같은 참조를 유지한다.

커스텀 자료형들은 따로 Object를 상속받은게 없는데 어떻게 위와 같은 함수들을 사용할 수 있는 걸까?

C#에서 모든 객체는 System.Object 클래스를 암묵적으로 상속받기 때문에, 위와 같은 메서드를 사용할 수 있다.
암묵적 상속은 C#에서 명시적으로 상속하지 않아도 모든 클래스가 자동으로 System.Object 클래스를 상속받는 것처럼, 언어 자체가 내부적으로 처리하는 방식이다. 즉, 개발자가 명시적으로 상속을 선언하지 않아도, 특정 클래스는 특정 부모 클래스의 기능을 자동으로 상속받는 개념이다.
이 과정은 언어 수준에서 제공되는 기본적인 기능으로, 개발자가 따로 이를 구현하거나 명시할 필요는 없다.

마무리

C#의 object 타입은 잘 쓰이지는 않지만 모든 자료형들의 조상이기 때문에 배워두면 유용하다. 또한 다형성을 지원하여 가끔 사용되기도 한다. 그러나 박싱/언박싱은 성능 문제를 야기할 수 있기 때문에 신중히 사용하거나, 대체 가능하다면 제네릭을 이용하는 편이 좋다.

profile
게임 개발을 하며 배운 것과 경험한 것을 기록하는 공간입니다.

0개의 댓글