C# 제네릭 (C# Generics)
일반적으로 클래스를 정의할 때, 클래스 내의 모든 데이타 타입을 지정해 주게 된다. 하지만 어떤 경우는 클래스의 거의 모든 부분이 동일한데 일부 데이타 타입만이 다른 경우가 있을 수 있다. 예를 들어, 사칙연산을 하는 클래스 A가 있다고 가정하자. 이 클래스 A에는 int 타입의 필드들이 있고, int 타입을 파라미터로 받아 계산하는 메서드들도 있다. 그러면 이 클래스 A를 double 타입의 데이타를 가지고 사용할 수 있을까? 그렇 수 없다. 왜냐하면 이미 모든 필드 및 파라미터가 int로 설정되어 있기 때문이다.
이런 경우 C#의 제네릭 타입(Generic Type)을 사용할 수 있는데, 제네릭 타입에서는 int, float, double 같은 데이타 요소 타입을 확정하지 않고 이 데이타 타입 자체를 타입파라미터(Type Parameter)로 받아들이도록 클래스를 정의한다. 이렇게 정의된 클래스 즉 C# 제네릭 타입을 사용할 때는 클래스명과 함께 구체적인 데이타 타입을 함께 지정해 주게 된다. 이렇게 하면 일부 상이한 데이타 타입 때문에 여러 개의 클래스들을 따로 만들 필요가 없어지게 된다. C# 제네릭은 이렇게 클래스 이외에도 인터페이스나 메서드에도 적용될 수 있다.
요약하면, C# 제네릭은 C++의 템플릿과 비슷한 (주: 내부 아키텍쳐는 상당한 차이점이 있다) 개념으로서 클래스, 인터페이스, 메서드 등에 <T> 같은 타입 파라미터를 붙여 구현한다. 사용시에는 이 타입 파라미터에 특정 타입을 지정하게 되는데, 실행(Runtime)시에 제네릭 타입(Generic Type)으로부터 지정된 타입의 객체(object)를 구체적으로 생성해서 사용하게 된다. 타입 파라미터는 하나 이상 여러 개를 지정할 수도 있다.
이런 경우 C#의 제네릭 타입(Generic Type)을 사용할 수 있는데, 제네릭 타입에서는 int, float, double 같은 데이타 요소 타입을 확정하지 않고 이 데이타 타입 자체를 타입파라미터(Type Parameter)로 받아들이도록 클래스를 정의한다. 이렇게 정의된 클래스 즉 C# 제네릭 타입을 사용할 때는 클래스명과 함께 구체적인 데이타 타입을 함께 지정해 주게 된다. 이렇게 하면 일부 상이한 데이타 타입 때문에 여러 개의 클래스들을 따로 만들 필요가 없어지게 된다. C# 제네릭은 이렇게 클래스 이외에도 인터페이스나 메서드에도 적용될 수 있다.
요약하면, C# 제네릭은 C++의 템플릿과 비슷한 (주: 내부 아키텍쳐는 상당한 차이점이 있다) 개념으로서 클래스, 인터페이스, 메서드 등에 <T> 같은 타입 파라미터를 붙여 구현한다. 사용시에는 이 타입 파라미터에 특정 타입을 지정하게 되는데, 실행(Runtime)시에 제네릭 타입(Generic Type)으로부터 지정된 타입의 객체(object)를 구체적으로 생성해서 사용하게 된다. 타입 파라미터는 하나 이상 여러 개를 지정할 수도 있다.
예제
// 어떤 요소 타입도 받아들 일 수 있는
// 스택 클래스를 C# 제네릭을 이용하여 정의
class MyStack<T>
{
T[] _elements;
int pos = 0;
public MyStack()
{
_elements = new T[100];
}
public void Push(T element)
{
_elements[++pos] = element;
}
public T Pop()
{
return _elements[pos--];
}
}
// 두 개의 서로 다른 타입을 갖는 스택 객체를 생성
MyStack<int> numberStack = new MyStack<int>();
MyStack<string> nameStack = new MyStack<string>();
// 스택 클래스를 C# 제네릭을 이용하여 정의
class MyStack<T>
{
T[] _elements;
int pos = 0;
public MyStack()
{
_elements = new T[100];
}
public void Push(T element)
{
_elements[++pos] = element;
}
public T Pop()
{
return _elements[pos--];
}
}
// 두 개의 서로 다른 타입을 갖는 스택 객체를 생성
MyStack<int> numberStack = new MyStack<int>();
MyStack<string> nameStack = new MyStack<string>();
.NET Generic 클래스들
.NET Framework에는 상당히 많은 제네릭 클래스들이 포함되어 있는데, 특히 System.Collections.Generic 네임스페이스에 있는 모든 자료구조 관련 클래스들은 제네릭 타입이다. 흔히 사용하는 List<T>, Dictionary<T>, LinkedList<T> 등의 클래스들은 이 네임스페이스 안에 들어 있다. 아래는 이들을 사용한 한 예이다.
예제
List<string> nameList = new List<string>();
nameList.Add("홍길동");
nameList.Add("이태백");
Dictionary<string, int> dic = new Dictionary<string, int>();
dic["길동"] = 100;
dic["태백"] = 90;
nameList.Add("홍길동");
nameList.Add("이태백");
Dictionary<string, int> dic = new Dictionary<string, int>();
dic["길동"] = 100;
dic["태백"] = 90;
제네릭 타입 제약 (Type Constraint)
C# 제네릭 타입을 선언할 때, 타입 파라미터가 Value Type인지 Reference Type인지, 또는 어떤 특정 Base 클래스로부터 파생된 타입인지, 어떤 인터페이스를 구현한 타입인지 등등을 지정할 수 있는데, 이는 where T : 제약조건 과 같은 식으로 where 뒤에 제약 조건을 붙이면 가능하다. 아래는 다양한 제약을 가한 예제들이다.
예제
// T는 Value 타입
class MyClass<T> where T : struct
// T는 Reference 타입
class MyClass<T> where T : class
// T는 디폴트 생성자를 가져야 함
class MyClass<T> where T : new()
// T는 MyBase의 파생클래스이어야 함
class MyClass<T> where T : MyBase
// T는 IComparable 인터페이스를 가져야 함
class MyClass<T> where T : IComparable
// 좀 더 복잡한 제약들
class EmployeeList<T> where T : Employee,
IEmployee, IComparable<T>, new()
{
}
// 복수 타입 파라미터 제약
class MyClass<T, U>
where T : class
where U : struct
{
}
class MyClass<T> where T : struct
// T는 Reference 타입
class MyClass<T> where T : class
// T는 디폴트 생성자를 가져야 함
class MyClass<T> where T : new()
// T는 MyBase의 파생클래스이어야 함
class MyClass<T> where T : MyBase
// T는 IComparable 인터페이스를 가져야 함
class MyClass<T> where T : IComparable
// 좀 더 복잡한 제약들
class EmployeeList<T> where T : Employee,
IEmployee, IComparable<T>, new()
{
}
// 복수 타입 파라미터 제약
class MyClass<T, U>
where T : class
where U : struct
{
}
C# 동영상 강의 : C# 제네릭 개념과 사용법 [레벨] 중급 [C# 중급] C# 제네릭 타입의 일반적인 개념을 설명하고, 제네릭 클래스를 만들어 사용하는 방법에 대해 자세히 소개합니다. 제네릭 타입을 설명하는 예제에서 필요로 하여 C# dynamic 을 사용한 동적 타입에 대해서도 함께 설명합니다. |