C# 제네릭(Generics) 완벽 가이드: 유연하고 타입 안전한 코드 작성
제네릭(Generics)은 클래스나 메서드에서 프로그래밍 요소의 데이터 형식 사양을 실제 프로그램에서 사용될 때까지 정의하지 않고 미루는 것을 허용합니다. 즉, 제네릭을 사용하면 모든 데이터 형식과 작동하는 클래스나 메서드를 작성할 수 있습니다.
데이터 형식에 대한 대체 매개 변수를 사용하여 클래스나 메서드의 사양을 작성합니다. 컴파일러가 클래스의 생성자나 메서드의 함수 호출을 만나면 특정 데이터 형식을 처리하는 코드를 생성합니다. 간단한 예제로 개념을 이해해 보겠습니다.
Live Demo
using System;
using System.Collections.Generic;
namespace GenericApplication {
public class MyGenericArray<T> {
private T[] array;
public MyGenericArray(int size) {
array = new T[size + 1];
}
public T getItem(int index) {
return array[index];
}
public void setItem(int index, T value) {
array[index] = value;
}
}
class Tester {
static void Main(string[] args) {
// int 배열 선언
MyGenericArray<int> intArray = new MyGenericArray<int>(5);
// 값 설정
for (int c = 0; c < 5; c++) {
intArray.setItem(c, c * 5);
}
// 값 검색
for (int c = 0; c < 5; c++) {
Console.Write(intArray.getItem(c) + " ");
}
Console.WriteLine();
// 문자 배열 선언
MyGenericArray<char> charArray = new MyGenericArray<char>(5);
// 값 설정
for (int c = 0; c < 5; c++) {
charArray.setItem(c, (char)(c + 97));
}
// 값 검색
for (int c = 0; c < 5; c++) {
Console.Write(charArray.getItem(c) + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
0 5 10 15 20 a b c d e
제네릭의 특징
제네릭은 다음과 같은 방식으로 프로그램을 향상시킵니다.
코드 재사용성, 타입 안전성, 성능을 극대화합니다.
제네릭 컬렉션 클래스를 생성할 수 있습니다. .NET Framework 클래스 라이브러리에는 System.Collections.Generic 네임스페이스의 여러 새로운 제네릭 컬렉션 클래스가 포함되어 있습니다. System.Collections 네임스페이스의 컬렉션 클래스 대신 이 제네릭 컬렉션 클래스를 사용할 수 있습니다.
자체 제네릭 인터페이스, 클래스, 메서드, 이벤트, 대리자를 생성할 수 있습니다.
특정 데이터 형식의 메서드에 접근할 수 있도록 제약이 있는 제네릭 클래스를 생성할 수 있습니다.
리플렉션을 통해 런타임에 제네릭 데이터 형식에 사용된 형식 정보를 얻을 수 있습니다.
제네릭 메서드
앞의 예제에서는 제네릭 클래스를 사용했습니다. 타입 매개 변수를 사용하여 제네릭 메서드를 선언할 수도 있습니다. 다음 프로그램은 이 개념을 보여줍니다.
Live Demo
using System;
using System.Collections.Generic;
namespace GenericMethodAppl {
class Program {
static void Swap<T>(ref T lhs, ref T rhs) {
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
static void Main(string[] args) {
int a, b;
char c, d;
a = 10; b = 20;
c = 'I'; d = 'V';
// 스왑 전 값 출력
Console.WriteLine("Int values before calling swap:");
Console.WriteLine("a = {0}, b = {1}", a, b);
Console.WriteLine("Char values before calling swap:");
Console.WriteLine("c = {0}, d = {1}", c, d);
// 스왑 호출
Swap<int>(ref a, ref b);
Swap<char>(ref c, ref d);
// 스왑 후 값 출력
Console.WriteLine("Int values after calling swap:");
Console.WriteLine("a = {0}, b = {1}", a, b);
Console.WriteLine("Char values after calling swap:");
Console.WriteLine("c = {0}, d = {1}", c, d);
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
Int values before calling swap: a = 10, b = 20
Char values before calling swap: c = I, d = V
Int values after calling swap: a = 20, b = 10
Char values after calling swap: c = V, d = I
제네릭 대리자
타입 매개 변수를 사용하여 제네릭 대리자를 정의할 수 있습니다. 예를 들어:
delegate T NumberChanger<T>(T n);
다음 예제는 이 대리자의 사용을 보여줍니다.
Live Demo
using System;
using System.Collections.Generic;
delegate T NumberChanger<T>(T n);
namespace GenericDelegateAppl {
class TestDelegate {
static int num = 10;
public static int AddNum(int p) {
num += p;
return num;
}
public static int MultNum(int q) {
num *= q;
return num;
}
public static int getNum() {
return num;
}
static void Main(string[] args) {
// 대리자 인스턴스 생성
NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
// 대리자 객체를 사용하여 메서드 호출
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
Value of Num: 35
Value of Num: 175