CSharp Generics

양승준·2025년 4월 17일

CSharp

목록 보기
17/20
post-thumbnail

시작하며

이번에 정리할 것은 C#에서 자주 사용되는 핵심 개념인 object, 박싱/언박싱, 그리고 제네릭(Generics)에 대해
개념, 차이점, 사용 사례, 주의사항, 예제 코드 등을 정리한다.


📦 1. object란?

정의

  • C#에서 모든 타입의 최상위 부모 클래스
  • 모든 클래스, 구조체, 열거형 등은 object를 상속함
object a = 123;              // int
object b = "Hello";          // string
object c = new DateTime();   // DateTime

사용 목적

  • 다양한 타입을 하나의 변수에 담기 위해
  • 데이터 타입이 고정되지 않은 경우 (예: 비제네릭 컬렉션, 동적 처리 등)

단점

  • 타입 안정성 부족
  • 박싱/언박싱이 발생할 수 있어 성능 저하
  • 형변환 오류 위험 (InvalidCastException)


object 사용 예 (인터넷 통신/직렬화)

// JSON 역직렬화 후 object 타입으로 받기
string json = "{ \"Name\": \"Alice\", \"Age\": 25 }";
object result = JsonConvert.DeserializeObject(json);

// 타입 확인 후 캐스팅
if (result is JObject jObj)
{
    Console.WriteLine(jObj["Name"]);
}
// ASP.NET Core API 예시
[HttpGet]
public object GetData()
{
    return new { Message = "Hello", Code = 200 };
}

로 사용한다고한다... 사용안해봤는데 나중에 사용해봐야겠다.


언제 object를 쓸까?

  • 정말 다양한 타입을 하나로 처리해야 할 때
  • 런타임에 타입이 정해지는 경우
  • 직렬화/역직렬화 또는 API 응답 포맷 등

🔄 2. 박싱(Boxing) & 언박싱(Unboxing)

박싱(Boxing)

값 타입(int, float, struct)을 참조 타입(object)으로 변환

int x = 10;
object obj = x; // Boxing 발생
  • x의 값을 힙에 복사하고, obj는 그 참조를 가짐
  • 조금더 상세히 정리하면
    • object obj = x; 문장에서 x의 값의 주소로 이동해서 값을 확인하고, 그 값이 System.Int32 구조체고 그렇기 때문에 힙에 System.Int32 구조체 의 값을 할당하고 그 힙에 할당된 주소를 obj에 할당 하므로서 박싱이 일어나 주소가 저장되었다고 말하는 것이다.


언박싱(Unboxing)

object 타입으로 박싱된 값을 다시 원래 값 타입으로 변환

int y = (int)obj; // 언박싱
  • 반드시 명시적 캐스팅 필요
  • 타입이 다르면 런타임 오류 발생
  • 조금 더 상세히 정리
    • 처음 박싱 되었던 객체(obj)를 언박싱하려고 할 때, 해당 객체를 호출(힙에 저장된 위치 가져와서)해서 해당 값이 형태가 지금 형변환(명시적 형변환)하려고 하는 것과 같은지 확인한다. 그 다음 맞다면 값을 복사하여 형변환을 진행하고 아니라면 오류를 발생 시키는 것이다. 왜 정확한 형변환이 필요하냐면 오류의 발생을 막기 위함도 있지만, 구조체의 형태가 변경되는 이유이기 때문이다. 그래서 아래와 같이 먼저 int로 변환 후에 float로 한번 더 변환을 진행하는 것이 그러한 박싱되어 있던 원래 형태를 변환후 한번더 변환을 진행하는 이유이다. 그리고 obj 변수가 더 이상 필요없거나 사용되지 않는 단계까지 된다면 GC가 자동으로 수거해간다.
float z = (float)obj; // ❌ InvalidCastException
float z = (float)(int)obj; // ✅ OK

박싱/언박싱 문제점

  • 런타임 성능 저하
  • 메모리 사용 증가 (힙 할당)
  • 타입 캐스팅 오류 발생 가능

3. 제네릭(Generics)

정의

하나의 클래스/메서드를 다양한 타입으로 재사용할 수 있게 해주는 기능

public void Print<T>(T value)
{
    Console.WriteLine(value);
}

제네릭의 장점

장점설명
재사용성타입별로 코드를 반복 작성할 필요 없음
타입 안정성컴파일 타임에 타입 체크
성능 향상박싱/언박싱 없음
가독성코드가 간결해지고 중복 제거 가능



제네릭 Swap 예제

// 같은 기능의 일반 오버로드 함수들
public static void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}
public static void Swap(ref float a, ref float b)
{
    float temp = a;
    a = b;
    b = temp;
}
public static void Swap(ref byte a, ref byte b)
{
    byte temp = a;
    a = b;
    b = temp;
}


// 이러한 식으로, 위의 함수를 대체할 수 있다.
public static void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

사용:

int x = 1, y = 2;
Swap<int>(ref x, ref y);



제네릭의 Where 제약 조건

where이라는 키워드는 T에 사용할 수 있는 타입의 조건을 제한하는 키워드다.

  • 제내릭 타입을 너무 자유롭게 쓰면 위험할 수 있기에 제약을 걸 용도이다.
  • where을 사용해 타입 안정성을 더 강화할 수 있다.

여러 용도의 제약을 걸수 있다.

제약 조건의미예시
where T : class참조형 타입만 가능string, List, MyClass
where T : struct값 타입만 가능int, float, DateTime, 사용자 정의 struct 등
where T : new()매개변수 없는 생성자가 있어야 함new T() 가능해야 할 때
where T : BaseClass특정 클래스 혹은 그 자식 클래스만 가능다형성 처리
where T : InterfaceName특정 인터페이스를 구현한 타입만 가능IDisposable, IComparable 등
여러 제약 조건동시에 적용 가능 (,로 연결)where T : class, new() 등

예제 코드

// 클래스만 매개변수로 받고 싶을 때
public void Log<T>(T data) where T : class
{
    Console.WriteLine(data?.ToString());
}

// 구조체만 매개변수로 받으려고 할 때
public T Add<T>(T a, T b) where T : struct
{
    return default(T); // 예시용
}

// 특정 인터페이스 정의한 클래스 또는 구조체를 받으려고할 때
public T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

4. object vs 제네릭 비교

항목object제네릭
타입 안정성낮음 (형변환 필요)높음 (컴파일 타임 확인)
박싱/언박싱발생함없음
성능상대적으로 느림빠름
사용 난이도쉬움다소 복잡
대표 예ArrayList, object, varList<T>, Dictionary<K,V>, Swap<T>()

결론

  • object는 유연하지만 타입 안정성 & 성능 저하의 단점이 존재한다.
  • 박싱/언박싱은 코드 상 자연스러워 보여도, 성능에 영향을 줄 수 있음
  • 가능한 경우에는 제네릭으로 대체하는 것이 C# 개발에서 바람직하다.

profile
지모창말, 미모창.

0개의 댓글