개요
안녕하세요. 이번 글에서는 C#의 제네릭(Generic)에 대해 알아보겠습니다. 제네릭은 타입을 나중에 정하도록 비워 두는 기능입니다. List<int>, Dictionary<string, int>처럼 꺾쇠(< >) 안에 타입을 넣는 것이 모두 제네릭입니다. 하나의 코드로 여러 타입을 안전하게 재사용할 수 있습니다.
제네릭이 없으면 생기는 문제
제네릭 이전에는 여러 타입을 담으려고 object를 썼습니다. 그런데 object는 무엇이든 담기지만, 꺼낼 때 형변환이 필요하고 타입 실수를 컴파일 때 못 잡습니다.
class Box
{
public object Value; // 아무 타입이나 담김
}
Box b = new Box();
b.Value = 10;
int n = (int)b.Value; // 꺼낼 때 형변환 필요
b.Value = "hello";
int m = (int)b.Value; // 컴파일은 되지만 실행 중 오류! (string→int)
이처럼 object는 타입 안전하지 않고, 값 형식은 박싱(boxing) 비용도 생깁니다. 제네릭은 이 문제를 해결합니다.
제네릭 클래스
클래스 이름 뒤에 <T>를 붙이면, T는 “나중에 정할 타입”을 뜻하는 자리표시자가 됩니다. 사용할 때 실제 타입을 지정합니다.
class Box<T> // T는 타입 매개변수
{
public T Value;
}
Box<int> b1 = new Box<int>();
b1.Value = 10; // int만 담김
// b1.Value = "hi"; // 컴파일 에러로 바로 잡힘
Box<string> b2 = new Box<string>();
b2.Value = "hello";
string s = b2.Value; // 형변환 불필요
T 자리에 int를 넣으면 int 전용 Box, string을 넣으면 string 전용 Box가 됩니다. 형변환이 필요 없고, 타입 실수는 컴파일 단계에서 걸러집니다.
제네릭 메서드
클래스 전체가 아니라 메서드 하나만 제네릭으로 만들 수도 있습니다. 메서드 이름 뒤에 <T>를 붙입니다.
// 두 값을 서로 바꾸는 메서드 (어떤 타입이든)
void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(x, y); // T가 int로 자동 추론됨 → x=2, y=1
호출할 때 인자를 보고 T가 자동으로 추론되므로, 보통 <int>를 따로 적지 않아도 됩니다.
타입 제약 — where
T에 아무 타입이나 오면 할 수 있는 일이 제한됩니다. where로 “T는 이런 조건을 만족해야 한다”는 제약을 걸 수 있습니다.
// T는 IComparable을 구현한 타입만 허용
T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b; // 비교 가능 보장
}
| 제약 | 의미 |
|---|---|
| where T : class | 참조 형식만 |
| where T : struct | 값 형식만 |
| where T : new() | 매개변수 없는 생성자가 있어야 |
| where T : 인터페이스/클래스 | 그 타입을 상속·구현해야 |
정리
- 제네릭은 타입을 나중에 정하도록 비워 두는 기능 (<T>).
- object와 달리 형변환이 필요 없고, 타입 안전하며, 박싱 비용이 없다.
- 제네릭 클래스(Box<T>)와 제네릭 메서드(Swap<T>)로 쓸 수 있다.
- where로 T에 조건(제약)을 걸 수 있다.
- List<T>, Dictionary<K,V> 같은 컬렉션이 모두 제네릭이다.
제네릭은 “하나의 코드로 여러 타입을 안전하게 재사용”하기 위한 기능입니다. 컬렉션을 쓸 때 이미 매일 사용하고 있는 셈입니다. 이상으로 C# 제네릭에 대해 간단히 알아보았습니다.