C#의 제네릭(Generic)
1. 개념 요약
제네릭(Generic) 은 클래스, 구조체, 인터페이스, 메서드 등에 대해 타입을 매개변수로 일반화(generalize) 하여 컴파일 타임에 타입을 지정할 수 있도록 하는 기능이다.
1
2
3
4
| public class List<T>
{
public void Add(T item) { /* ... */ }
}
|
여기서 T는 타입 매개변수(Type Parameter) 로, 컴파일 시점에 실제 타입으로 대체된다.
2. 제네릭의 핵심 목적
| 목적 | 설명 |
|---|
| 타입 안정성 | 컴파일 시점에 타입 오류를 검출함 (object 기반 컬렉션과 비교) |
| 재사용성 | 다양한 타입에 대해 하나의 코드로 동작하도록 구현 가능 |
| 성능 향상 | 박싱/언박싱(특히 값 타입)에 의한 런타임 오버헤드 제거 |
| 가독성 및 유지보수 | 반복 코드 제거 → 코드 중복 최소화, 유지보수성 향상 |
3. 사용 가능한 대상
| 대상 | 예시 |
|---|
| 클래스 | public class Stack<T> {} |
| 구조체 | public struct Pair<T1, T2> {} |
| 인터페이스 | interface IRepository<T> {} |
| 메서드 | public T Max<T>(T a, T b) |
| 델리게이트 | public delegate void Action<T>() |
4. 사용 예시
클래스
1
2
3
4
5
6
| public class Repository<T>
{
private List<T> items = new List<T>();
public void Add(T item) => items.Add(item);
public T Get(int index) => items[index];
}
|
메서드
1
2
3
4
| public static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
|
5. 제네릭 제약 조건 (Constraints)
주요 제약 키워드
| 키워드 | 설명 |
|---|
where T : class | 참조형만 허용 |
where T : struct | 값형만 허용 |
where T : new() | 매개변수 없는 생성자 필요 |
where T : BaseClass | 특정 클래스 또는 인터페이스 상속 필요 |
where T : interface | 특정 인터페이스 구현 필요 |
예시
1
2
3
4
| public class Factory<T> where T : new()
{
public T Create() => new T();
}
|
6. 런타임 vs 컴파일타임 제네릭
- C#의 제네릭은 CLR 수준에서 구현된 정적 제네릭
- 값 타입 T마다 별도 코드 생성됨 (JIT specialization)
- 참조 타입 T는 공통 IL 코드 공유
- Java와 달리 C#은 리화 타입(reified types) 지원
7. 제네릭의 응용
제네릭 인터페이스 기반 DI 설계
1
2
3
4
5
| public interface IRepository<T>
{
void Add(T item);
T GetById(int id);
}
|
제네릭 메서드와 타입 추론
1
2
3
4
5
6
| public T Echo<T>(T value)
{
return value;
}
var result = Echo("hello"); // 컴파일러가 T를 string으로 추론
|
제네릭 Delegate/Action/Func
1
2
| Func<int, string> intToString = x => x.ToString();
Action<MyType> print = t => Console.WriteLine(t);
|
8. 주의점
| 주의 사항 | 설명 |
|---|
| 런타임 타입 정보가 제한적 | typeof(T)는 가능하지만, T의 멤버에 접근하려면 리플렉션 필요 |
| 제네릭 타입 파라미터는 static 멤버 공유 불가 | static 필드는 타입별로 개별 생성됨 |
| 박싱/언박싱 줄일 수 있지만 참조 타입엔 영향 없음 | List도 내부적으로는 참조 배열 |
9. 결론 요약
- C#의 제네릭은 컴파일 타임 타입 안전성, 재사용성, 성능 최적화를 위한 핵심 기능
- T, T1, T2, TKey, TValue 등의 타입 파라미터를 통해 범용 코드를 정의할 수 있음
- 제약 조건(where)을 통해 타입에 대한 요구사항을 명시할 수 있음
- 실무에서는 컬렉션, DI 패턴, 유틸리티 메서드, 서비스 추상화 등 다양한 곳에 활용됨
- C#의 제네릭은 리화 타입 기반으로 값 타입에서도 박싱 없이 최적화됨