[로봇활용_10주차] C# 제네릭(Generic)이란?

최윤호·2025년 10월 14일
post-thumbnail

제네릭(Generic): 만능 상자

"정수(int) 두 개를 바꾸는 메서드를 만들었는데,
이번에는 문자열(string) 두 개를 바꿔야 하네?
하는 일은 똑같은데, 형식만 다르다고 메서드를 따로 만들어야 하나요?"

똑같은 로직을 형식(Type)만 바꿔서 반복적으로 작성하는 건 비효율적입니다.
이럴 때 C#에서는 '만능 상자'를 만드는 도구, 제네릭(Generic)을 제공합니다.

1)제네릭의 필요성

먼저 제네릭이 왜 필요한지, 그 불편함을 직접 느껴보는 것부터 시작하겠습니다.
두 개의 변수 값을 서로 맞바꾸는 Swap메서드를 만들어 볼게요.

[제네릭을 사용하기 전]

// 1. 정수(int)를 교환하는 메서드
static void SwapInt(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

// 2. 문자열(string)을 교환하는 메서드
static void SwapString(ref string a, ref string b)
{
    string temp = a;
    a = b;
    b = temp;
}

// ... double, float, Person 객체 등등 타입마다 계속 만들어야 함 ...

단지 매개변수의 형식이 다르다는 이유로 중복 코드가 계속 발생합니다.
이건 객체 지향의 DRY(Don't Repeat Yourself) 원칙에도 어긋나죠.

"여기서 object 형식을 사용하면 해결할 수 있지 않나요?"

좋은 생각일까요? 한번 코드를 수정해 봅시다.

static void SwapObject(ref object a, ref object b)
{
    object temp = a;
    a = b;
    b = temp;
}

이제 SwapObject메서드 하나로 정수든, 문자열이든 교환할 수 있게 되었습니다.
정말 문제가 해결된 건지 코드를 통해 자세히 살펴보겠습니다.

[object 형식을 사용해서 해결?]

class Program
{
    static void Main()
    {
        int num1 = 10;
        int num2 = 20;

        // 1. int에서 object로 '박싱(Boxing)' 발생
        object obj1 = num1;
        object obj2 = num2;

        SwapObject(ref obj1, ref obj2);

        // 2. object에서 int로 '언박싱(Unboxing)' 발생
        num1 = (int)obj1;
        num2 = (int)obj2;

        Console.WriteLine($"num1: {num1}, num2: {num2}");

        int number = 100;
        string text = "Hello";

        object obj3 = number;
        object obj4 = text;

        SwapObject(ref obj3, ref obj4);

        // 프로그래머가 실수로 다른 타입을 교환하려고 하면 어떻게 될까요?
        number = (int)obj3; // 런타임 오류 발생! o1에는 "Hello"가 들어있음
    }

    static void SwapObject(ref object a, ref object b)
    {
        object temp = a;
        a = b;
        b = temp;
    }
}

[실행 결과]

num1: 20, num2: 10
❌예외 발생 System.InvalidCastException: 'Unable to cast...

이 코드에는 두 가지 심각한 문제가 숨어있습니다.

  • 성능 저하 (박싱/언박싱)
    값 형식(Value Type)을 object로 변환하면 박싱/언박싱이 발생합니다.
    박싱/언박싱 과정에서 눈에 띄는 성능 저하를 일으킬 수 있습니다.

  • 타입 불안정성 (컴파일 시점에 타입 오류를 못 잡음)
    컴파일러는 object에 원래 어떤 형식이 들어있었는지 신경 쓰지 않습니다.
    컴파일은 통과하지만, 잘못된 형변환 시 런타임 오류가 발생할 수 있습니다.
    이런 종류의 버그는 찾아내기도 어렵고 매우 치명적입니다.

이런 문제들을 해결하며 코드의 재사용성을 극대화하는 것이 바로 제네릭입니다.

2)제네릭 메서드

제네릭 메서드는 다양한 타입을 처리할 수 있는 '만능 메서드'입니다.
메서드 이름 뒤에 꺾쇠괄호 < >를 붙이고, 그 안에 타입 매개변수를 넣어 정의합니다.
타입 매개변수는 보통 T를 사용하는데, 'Type'의 약자입니다.

[코드]

class Program
{
    static void Main()
    {
        int num1 = 10;
        int num2 = 20;
        Swap(ref num1, ref num2); // 컴파일러가 T를 int로 추론
        Console.WriteLine($"num1: {num1}, num2: {num2}");

        string str1 = "Hello";
        string str2 = "World";
        Swap(ref str1, ref str2); // 컴파일러가 T를 string으로 추론
        Console.WriteLine($"str1: {str1}, str2: {str2}");
    }

    // <T>를 사용하여 "어떤 타입 T든 들어올 수 있다"고 선언
    static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
}

[실행 결과]

num1: 20, num2: 10
str1: World, str2: Hello

타입 안정성과 코드 재사용성, 두 마리 토끼를 모두 잡았습니다!

3)제네릭 클래스

메서드뿐만 아니라 클래스도 '만능 클래스'로 만들 수도 있습니다.
제네릭 클래스가 다루는 데이터의 타입을 인스턴스를 생성할 때 결정하도록 합니다.
이때 List<T>, Dictionary<TKey, TValue>가 바로 대표적인 제네릭 클래스입니다.
어떤 타입이든 담을 수 있는 Box<T>클래스를 만들어 봅시다.

[코드]

// 클래스 이름 뒤에 <T>를 붙여 일반화 클래스로 선언
public class Box<T>
{
    // 내부 필드도 T 타입으로 선언
    private T _item;

    public void SetItem(T item)
    {
        _item = item;
    }

    public T GetItem()
    {
        return _item;
    }
}

class Program
{
    static void Main()
    {
        // 클래스를 사용할 때는 T에 들어갈 실제 타입을 명시적으로 지정해 주어야 합니다.

        // 1. 정수를 담는 상자
        Box<int> intBox = new Box<int>();
        intBox.SetItem(123);
        int number = intBox.GetItem();
        Console.WriteLine($"정수 상자 속 아이템: {number}");

        // 2. 문자열을 담는 상자
        Box<string> stringBox = new Box<string>();
        stringBox.SetItem("Generic Class!");
        string message = stringBox.GetItem();
        Console.WriteLine($"문자열 상자 속 아이템: {message}");

        // Box<int>와 Box<string>은 컴파일 시점에 완전히 다른 타입으로 취급됩니다.
        // stringBox.SetItem(456); // 컴파일 오류 발생!
    }
}

[실행 결과]

정수 상자 속 아이템: 123
문자열 상자 속 아이템: Generic Class!

4)요약

제네릭은 코드의 중복을 줄이고, 타입 안정성을 높여주는 필수적인 도구입니다.

  • 코드 재사용성: 타입별로 중복 코드를 작성할 필요가 없습니다.
  • 타입 안정성: 컴파일 시점에 타입 오류를 잡아내어 런타임 에러를 방지합니다.
  • 성능: object를 사용할 때 발생하는 박싱/언박싱이 없어 성능상 이점이 있습니다.
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글