정리가 필요한 카테고리(추후 정리)/C#,Unity

C#] 제네릭(Generic) - 1

TwinParadox 2017. 1. 24. 00:30
728x90

### 제네릭(Generic)


여러 형식들에서 재사용할 수 있는 코드를 작성하기 위한 메커니즘 중 하나로, 자리표(placehodler)에 해다하는 형식들을 담은 템플릿(template)을 통해 재사용성을 표현함.

상속에 비해 형식 안전성이 증가하고 casting, boxing이 줄어듦.



## 형식


형식 매개변수(type parameter)들을 선언하여 사용하며, 이는 실제 형식들이 대신할 자리를 표시하는 자리표에 해당함.

형식 매개변수는 관례적으로 T를 이용하며, 형식 매개변수가 여러 개인 경우, T로 시작하면서 명확히 구분할 수 있는 이름을 붙여 매개변수를 사용함.

실제 형식은 형식 인수(type argument)라고 부름.



ex) Stack<T>


1
2
3
4
5
6
7
8
9
10
11
12
public class Stack<T>
{
    int position;
    T[] data = new T[100];
    public void Push (T obj) => data[position++= obj;
    public T Pop() => data[--position];
}
var stack = new Stack<int>();
stack.Push(5);
stack.Push(10);
int x = stack.Pop();
int y = stack.Pop();
cs


이 코드에 int형 데이터가 아닌 문자열을 넣으려고 하는 시도가 있으면 컴파일 오류가 발생함.

컴파일러가 생성한 Stack<int>의 의미는 실제로 아래와 같은 코드로 받아들여짐.



1
2
3
4
int position;
int[] data = new int[100]
public void Push (int obj) => data[position++]=obj;
public int Pop() => data[--position];
cs



위에서 Stack<T>는 열린 형식(open type), Stack<int> 닫힌 형식(closed type)이라고 부름.

실행 시점에서 모든 제네릭 형식 인스턴스는 닫힌 형식이며, 모든 자리표 형식들이 채워진 상태이며,

T를 하나의 형식 매개변수로 정의하는 다른 클래스나 메서드 안에서는 적법하나, 그렇지 않은 경우엔 위법한 코드가 됨.




## 존재 이유


서로 다른 형식들에 대해 재사용할 수 있는 코드를 작성하기 위한 것.

예를 들어, 제네릭이라는 개념이 없는 상태에서 스택을 구현 하는 경우가 있다고 가정.



1. 타입에 맞는 개별적인 클래스를 모두 작성.

-> 코드의 중복이 심함



2. object를 원소 형식으로 사용해 일반화.

-> 박싱(boxing), 하향 캐스팅(downcasting)이 필수적임

-> 박싱, 하향 캐스팅은 모두 컴파일 시점에서 형식 점검이 일어나지 않으며, 안전성 문제 발생.


1
2
3
4
5
6
7
public class ObjectStack
{
    int position;
    object[] data = new object[10];
    public void Push (object obj) => data[position++= obj;
    public object Pop() => data[--position];
}
cs


스택의 구현을 일반화하면서, 형식 안전성을 보장하고, 캐스팅, 박싱을 줄이기 위해서 특정 원소 형식에 맞게 특화시키는 수단으로 제네릭이 적합함.

제네릭은 원소의 형식을 매개변수화하면서 일반화와 특수화를 지원함. 1, 2번의 장점을 모두 가져옴.




## 제네릭 메서드


서명 안에 형식 매개변수가 있는 메서드를 제네릭 메서드라고 함.

이를 이용하면 여러 근본적인 알고리즘들을 범용적 방식으로 구현할 수 있음.

보통의 경우 제네릭 메서드를 호출하는 경우, 컴파일러가 형식을 추론할 수 있어 형식 인수를 명시적으로 지정할 필요가 없음.

그러나, 모호함이 존재해 추론에 실패할 여지가 있을 경우 명시적인 지정이 필요.


제네릭 형식 안의 모든 메서드가 자동으로 제네릭 메서드가 되는 것은 아님.

명시적으로 <>를 이용해 형식 매개변수를 도입한 메서드만 제네릭 메서드로 간주됨.

상기 스택 구현 코드에서 Pop 메서드의 경우 T를 사용하기만 하기 때문에 제네릭 메서드가 아님.


C#에서 형식 매개변수를 채용할 수 있는 코드 구축 요소는 메서드와 형식뿐임.

속성이나 인덱서, 이벤트, 필드, 생성자, 연산자 등은 형식 매개변수 선언 불가.

이러한 구축 요소들도 자신이 속한 형식에 정의된 임의의 형식 매개변수를 사용하는 것은 가능함.




## 형식 매개변수의 선언


형식 매개변수는 클래스나 구조체, 인터페이스, 대리자, 메서드의 선언에서만 도입.

속성을 비롯, 그 외의 코드 구축 요소에서는 도입은 불가능하고 사용만 가능.

하나의 제네릭 형식이나 제네릭 메서드는 여러 개의 형식 매개변수 도입 가능.

제네릭 형식 이름과 제네릭 메서드 이름을 오버로딩(중복적재)하는 것도 가능함.




## typeof 연산자


실행시점에서 열린 제네릭 형식이 존재하지 않고, 모두 컴파일 과정에서 닫힘.

실행시점에서 바인딩이 결정되지 않은 제네릭 형식이 존재할 수는 있으며 이러한 형식은 하나의 Type 객체로 존재.

C#에서는 바인딩 결정되지 않은 제네릭 형식을 지정하는 수단은 typeof 뿐.




## 기본값


default 키워드를 이용해 제네릭 형식 매개변수의 인스턴스의 기본값을 지정할 수 있음.

참조 형식 기본 값은 null, 값 형식의 기본 값은 필드의 모든 비트를 0으로 설정한 결과.




## 제약


기본적으로 형식 매개변수에는 그 어떤 형식도 대입 가능.

형식 매개변수에 제약 조건을 지정해 대입할 수 있는 형식 인수를 제한하는 것도 가능.

아래는 사용 가능한 제약 조건들.


where T : BaseClass // 기반 클래스 제약

where T : interface // 인터페이스 제약

where T : class // 참조 형식 제약

where T : struct // 값 형식 제약(nullable 제외)

where T : new() // 매개 변수 없는 생성자 제약

where U : T // 적나라한 형식 제약


형식 제약조건은 형식 매개변수들이 정의된 곳이면 어디에나 지정 가능하며, 제네릭 형식뿐 아니라 제네릭 메서드에도 적용 가능.


# 기반 클래스 제약 : 형식 매개변수가 반드시 주어진 클래스 혹은 그 클래스의 파생 클래스여야 함.

# 인터페이스 제약 : 형식 매개변수가 반드시 주어진 인터페이스 구현해야 함.

# 클래스 제약, 구조체 제약 : 반드시 T가 각각 참조 형식 혹은 값 형식이어야 함.

# 매개변수 없는 생성자 : T에 반드시 매개변수 없는 공용 생성자가 존재해야 함.

# 적나라한 형식 제약 : 형식 매개변수가 다른 형식 매개변수와 같은 형식이거나 파생 형식이어야 함.

728x90

'정리가 필요한 카테고리(추후 정리) > C#,Unity' 카테고리의 다른 글

C#] Delegate(델리게이트, 대리자) - 1  (0) 2017.02.01
C#] 구조체(Structure)  (0) 2017.01.27
C#] 열거형(Enum Type)  (60) 2017.01.23
C#] 부분 형식, 부분 메서드  (0) 2017.01.18
C#] nameof  (0) 2017.01.17