제네릭(Generic)은 자료형(타입)을 일반화하여 코드의 재사용성과 유연성을 높이는 기능이다.
제네릭을 사용하면 특정 타입에 종속되지 않고, 다양한 타입에서 동작하는 클래스를 만들 수 있다.
제네릭이 필요한 이유는 다음과 같다.
제네릭 클래스를 사용하면 다양한 타입을 수용할 수 있는 클래스를 만들 수 있다.
예제: 일반적인 컬렉션 클래스
public class MyList<T>
{
private T[] items;
private int count = 0;
public MyList(int size)
{
items = new T[size];
}
public void Add(T item)
{
if (count < items.Length)
{
items[count] = item;
count++;
}
}
public T GetItem(int index)
{
return items[index];
}
}
사용 예시
MyList<int> intList = new MyList<int>(5);
intList.Add(10);
intList.Add(20);
Console.WriteLine(intList.GetItem(1)); // 20
MyList<string> stringList = new MyList<string>(3);
stringList.Add("Hello");
Console.WriteLine(stringList.GetItem(0)); // Hello
T는 사용자가 지정하는 타입이며, int나 string 등 어떤 타입도 가능하다.클래스 전체가 아니라 특정 메서드만 제네릭으로 만들 수도 있다.
예제: 두 값을 교환하는 제네릭 메서드
public class Utils
{
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
사용 예시
int x = 5, y = 10;
Utils.Swap(ref x, ref y);
Console.WriteLine($"x: {x}, y: {y}"); // x: 10, y: 5
string s1 = "Hello", s2 = "World";
Utils.Swap(ref s1, ref s2);
Console.WriteLine($"s1: {s1}, s2: {s2}"); // s1: World, s2: Hello
제네릭 인터페이스를 사용하면 다양한 타입을 지원하는 인터페이스를 만들 수 있다.
예제: 제네릭 인터페이스
public interface IRepository<T>
{
void Add(T item);
T Get(int id);
}
사용 예시
public class UserRepository : IRepository<string>
{
private Dictionary<int, string> users = new Dictionary<int, string>();
public void Add(string item)
{
int id = users.Count + 1;
users[id] = item;
}
public string Get(int id)
{
return users.ContainsKey(id) ? users[id] : null;
}
}
IRepository<string> repo = new UserRepository();
repo.Add("Alice");
repo.Add("Bob");
Console.WriteLine(repo.Get(1)); // Alice
Console.WriteLine(repo.Get(2)); // Bob
제네릭을 사용할 때 특정 조건을 강제하고 싶다면 제약 조건(Constraints)을 사용할 수 있다.
예제: 특정 클래스 상속 제한
public class BaseEntity
{
public int Id { get; set; }
}
public class Repository<T> where T : BaseEntity
{
private List<T> items = new List<T>();
public void Add(T item)
{
items.Add(item);
}
public T FindById(int id)
{
return items.FirstOrDefault(x => x.Id == id);
}
}
public class User : BaseEntity
{
public string Name { get; set; }
}
public class Product : BaseEntity
{
public string ProductName { get; set; }
}
사용 예시
Repository<User> userRepo = new Repository<User>();
userRepo.Add(new User { Id = 1, Name = "Alice" });
Console.WriteLine(userRepo.FindById(1)?.Name); // Alice
Repository<Product> productRepo = new Repository<Product>();
productRepo.Add(new Product { Id = 1, ProductName = "Laptop" });
Console.WriteLine(productRepo.FindById(1)?.ProductName); // Laptop
where T : BaseEntity를 사용하여 BaseEntity를 상속받은 클래스만 사용할 수 있도록 제한했다.| 개념 | 설명 |
|---|---|
| 제네릭 클래스 | 여러 타입을 지원하는 클래스 (class MyList<T> {}) |
| 제네릭 메서드 | 특정 메서드만 제네릭으로 사용 (public static void Swap<T>(ref T a, ref T b)) |
| 제네릭 인터페이스 | 다양한 타입을 지원하는 인터페이스 (interface IRepository<T> {}) |
| 제네릭 제약 조건 | 특정 타입만 허용 (where T : BaseEntity) |