[C#] 제네릭 (Generics) : 타입의 유연성과 안전성을 동시에
안녕하세요! 이번 포스트에서는 C#의 제네릭(Generics)에 대해 알아보겠습니다. 제네릭은 코드의 유연성과 타입 안전성을 동시에 제공하는 강력한 기능입니다. 이를 통해 재사용 가능하고, 안전하며, 효율적인 코드를 작성할 수 있습니다.
제네릭(Generics)이란 무엇인가요?
제네릭은 클래스, 구조체, 인터페이스, 메서드 등을 정의할 때, 데이터 타입을 일반화하는 기법입니다. 제네릭을 사용하면 데이터 타입에 의존하지 않는 코드를 작성할 수 있어, 다양한 데이터 타입을 처리할 수 있는 유연한 구조를 만들 수 있습니다.
제네릭의 장점
- 타입 안전성: 컴파일 시점에 타입을 체크하여 타입 관련 오류를 줄입니다.
- 코드 재사용성: 데이터 타입에 상관없이 동작하는 코드를 작성할 수 있어, 중복 코드를 줄이고 재사용성을 높입니다.
- 성능 향상: 박싱과 언박싱을 피하여 성능을 향상시킵니다.
제네릭 클래스
제네릭 클래스를 정의하려면 클래스 이름 뒤에 타입 매개변수를 추가합니다. 일반적으로 타입 매개변수는 T로 표시합니다.
제네릭 클래스 예시
public class GenericList<T>
{
private T[] items;
private int count;
public GenericList(int capacity)
{
items = new T[capacity];
count = 0;
}
public void Add(T item)
{
if (count < items.Length)
{
items[count++] = item;
}
else
{
Console.WriteLine("List is full");
}
}
public T Get(int index)
{
if (index >= 0 && index < count)
{
return items[index];
}
else
{
throw new IndexOutOfRangeException();
}
}
}
위 예시에서 GenericList<T> 클래스는 제네릭 타입 T를 사용하여 다양한 타입의 리스트를 만들 수 있습니다.
제네릭 메서드
제네릭 메서드는 메서드 이름 뒤에 타입 매개변수를 추가하여 정의합니다. 이를 통해 메서드 내부에서 다양한 타입을 처리할 수 있습니다.
제네릭 메서드 예시
public class GenericMethods
{
public void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
class Program
{
static void Main(string[] args)
{
GenericMethods methods = new GenericMethods();
int x = 1, y = 2;
methods.Swap(ref x, ref y);
Console.WriteLine($"x: {x}, y: {y}"); // x: 2, y: 1
string str1 = "Hello", str2 = "World";
methods.Swap(ref str1, ref str2);
Console.WriteLine($"str1: {str1}, str2: {str2}"); // str1: World, str2: Hello
}
}
위 예시에서 Swap<T> 메서드는 제네릭 타입 T를 사용하여 두 변수의 값을 교환합니다. 메서드를 호출할 때 타입을 지정할 필요가 없으며, 컴파일러가 인수의 타입을 자동으로 추론합니다.
제네릭 인터페이스
제네릭 인터페이스는 인터페이스 이름 뒤에 타입 매개변수를 추가하여 정의합니다. 이를 통해 다양한 타입을 처리할 수 있는 인터페이스를 만들 수 있습니다.
제네릭 인터페이스 예시
public interface IRepository<T>
{
void Add(T item);
T Get(int id);
}
public class GenericRepository<T> : IRepository<T>
{
private Dictionary<int, T> storage = new Dictionary<int, T>();
public void Add(T item)
{
int id = storage.Count + 1;
storage.Add(id, item);
}
public T Get(int id)
{
if (storage.ContainsKey(id))
{
return storage[id];
}
else
{
throw new KeyNotFoundException();
}
}
}
위 예시에서 IRepository<T> 인터페이스는 제네릭 타입 T를 사용하여 다양한 타입의 저장소를 정의할 수 있습니다. GenericRepository<T> 클래스는 이 인터페이스를 구현합니다.
제네릭 제약 조건
제네릭 타입 매개변수에 제약 조건을 추가하여 특정 타입만 허용할 수 있습니다. 제약 조건은 where 키워드를 사용하여 정의합니다.
제네릭 제약 조건 예시
public class GenericConstraints
{
public void Display<T>(T item) where T : IComparable<T>
{
Console.WriteLine(item);
}
}
class Program
{
static void Main(string[] args)
{
GenericConstraints constraints = new GenericConstraints();
constraints.Display(123); // 123 출력
constraints.Display("Hello"); // Hello 출력
}
}
위 예시에서 Display<T> 메서드는 제네릭 타입 T가 IComparable<T> 인터페이스를 구현해야 한다는 제약 조건을 가집니다. 이를 통해 T 타입이 비교 가능한 타입임을 보장합니다.
공변성과 반공변성
제네릭은 공변성(Covariance)과 반공변성(Contravariance)을 지원합니다. 이는 주로 제네릭 인터페이스와 델리게이트에서 사용됩니다.
- 공변성: 제네릭 타입이 특정 타입의 하위 타입을 허용합니다. out 키워드를 사용하여 정의됩니다.
- 반공변성: 제네릭 타입이 특정 타입의 상위 타입을 허용합니다. in 키워드를 사용하여 정의됩니다.
공변성과 반공변성 예시
public interface ICovariant<out T>
{
T GetItem();
}
public interface IContravariant<in T>
{
void SetItem(T item);
}
위 예시에서 ICovariant<out T> 인터페이스는 공변성을, IContravariant<in T> 인터페이스는 반공변성을 지원합니다.
공변성(Covariance)이란 무엇인가요?
공변성은 하위 타입 관계를 유지하면서 객체 간의 변환을 허용하는 것을 말합니다. C#에서는 주로 배열과 제네릭 인터페이스에서 사용됩니다. 공변성은 out 키워드를 사용하여 정의됩니다.
공변성 예시: 배열
class Animal { }
class Dog : Animal { }
class Program
{
static void Main(string[] args)
{
Dog[] dogs = new Dog[] { new Dog(), new Dog() };
Animal[] animals = dogs; // 배열 공변성
foreach (var animal in animals)
{
Console.WriteLine(animal.GetType().Name);
}
}
}
위 예시에서 Dog 배열을 Animal 배열에 할당할 수 있습니다. 이는 공변성을 통해 하위 타입 관계를 유지하면서 배열 간의 변환을 허용하는 것입니다.
반공변성(Contravariance)이란 무엇인가요?
반공변성은 상위 타입 관계를 유지하면서 객체 간의 변환을 허용하는 것을 말합니다. C#에서는 주로 제네릭 델리게이트에서 사용됩니다. 반공변성은 in 키워드를 사용하여 정의됩니다.
반공변성 예시: 델리게이트
delegate void Action<in T>(T obj);
class Program
{
static void Main(string[] args)
{
Action<Animal> animalAction = (Animal animal) =>
{
Console.WriteLine(animal.GetType().Name);
};
Action<Dog> dogAction = animalAction; // 델리게이트 반공변성
dogAction(new Dog());
}
}
위 예시에서 Action<Animal> 델리게이트를 Action<Dog> 델리게이트에 할당할 수 있습니다. 이는 반공변성을 통해 상위 타입 관계를 유지하면서 델리게이트 간의 변환을 허용하는 것입니다.
공변성과 반공변성의 사용 예시
공변성 예시: 배열
공변성은 배열과 같은 컬렉션에서 주로 사용됩니다. 하위 타입의 배열이 상위 타입의 배열로 취급될 수 있습니다.
Dog[] dogs = new Dog[] { new Dog(), new Dog() };
Animal[] animals = dogs; // 배열 공변성
foreach (var animal in animals)
{
Console.WriteLine(animal.GetType().Name);
}
반공변성 예시: 델리게이트
반공변성은 델리게이트와 같은 대리자 타입에서 주로 사용됩니다. 상위 타입의 델리게이트가 하위 타입의 델리게이트로 취급될 수 있습니다.
Action<Animal> animalAction = (Animal animal) =>
{
Console.WriteLine(animal.GetType().Name);
};
Action<Dog> dogAction = animalAction; // 델리게이트 반공변성
dogAction(new Dog());
공변성과 반공변성의 주의사항
- 공변성과 반공변성은 제네릭 타입 매개변수를 가진 인터페이스나 델리게이트에서만 사용할 수 있습니다.
- 공변성과 반공변성은 배열이나 대리자와 같은 특정 타입에 대해서만 가능합니다. 모든 경우에 적용되는 것은 아닙니다.
- 공변성은 출력 매개변수(리턴 타입)에 사용되고, 반공변성은 입력 매개변수에 사용됩니다.
마치며
이번 포스트에서는 C#의 제네릭에 대해 알아보았습니다. 제네릭은 코드의 유연성과 타입 안전성을 동시에 제공하여, 재사용 가능하고 효율적인 코드를 작성할 수 있게 합니다. 제네릭 클래스, 메서드, 인터페이스를 통해 다양한 데이터 타입을 처리할 수 있으며, 제네릭 제약 조건을 사용하여 타입 안전성을 강화할 수 있습니다. 추가적인 질문이나 궁금한 점이 있다면 언제든지 댓글로 남겨주세요!
'Programming > C#' 카테고리의 다른 글
C# 구조체(Struct) 경량화된 값 형식 (0) | 2024.06.09 |
---|---|
C# 인덱서(Indexer) 배열과 유사한 접근성 제공 (0) | 2024.06.09 |
C# 섀도잉(Shadowing)과 하이딩(Hiding) 멤버 숨김의 이해 (0) | 2024.06.09 |
C# 다형성 (Polymorphism) 객체지향 프로그래밍의 유연성과 확장성 (0) | 2024.06.09 |
C# 상속(Inheritance) 객체지향 프로그래밍의 핵심 개념 (0) | 2024.06.09 |