C# Chapter 11 Geneiric
이 글은 한빛미디어의 이것이 C#이다를 보고 헷갈리는 부분 위주로 정리한 글입니다.
제네릭
일반화 메소드
기본 형식
한정자 반환_형식 메소드_이름<형식_매개변수> (매개변수_목록)
{
...
}
예시
다음과 같은 메소드를 일반화를 사용하면
void CopyArray(int[] source, int[] target)
{
for (int i = 0; i < source.Length; i++)
{
target[i] = source[i];
}
}
void CopyArray(float[] source, float[] target)
{
for (int i = 0; i < source.Length; i++)
{
target[i] = source[i];
}
}
void CopyArray(string[] source, string[] target)
{
for (int i = 0; i < source.Length; i++)
{
target[i] = source[i];
}
}
이와 같이 바꿀 수 있다.
여기서 <형식_매개변수> 는 꼭 T일 필요 없으나형식_매개변수>
T를 쓰는 것이 일반적이다
void CopyArray<T>(T[] source, T[] target)
{
for (int i = 0; i < source.Length; i++)
{
target[i] = source[i];
}
}
사용할 때는 다음과 같이 사용한다
static void CopyArray<T>(T[] source, T[] target)
{
for (int i = 0; i < source.Length; i++)
{
target[i] = source[i];
}
}
static void Main(string[] args)
{
int[] A = { 1, 2, 3 };
int[] B = new int[A.Length];
CopyArray<int>(A, B); // 정석
CopyArray(A, B); // 매개변수의 자료형을 통해서 형식_매개변수의 자료형을
// 컴파일러가 충분히 유추가 가능하므로 <int>가 생략가능하다
}
일반화 클래스
기본 형식
class 클래스_이름 <형식_매개변수>
{
...
}
예시
다음과 같이 하나의 클래스를 정의하였지만
형식_매개변수로 넣어주는 <>사이의 자료형에 따라
다른 클래스를 생성한다.
class MyList<T>
{
private T[] array;
public MyList()
{
array = new T[3];
}
public T this[int index]
{
get { return array[index]; }
set
{
if (index >= array.Length)
{
Array.Resize(ref array, index + 1);
Console.WriteLine("array 리사이징 : {0}", array.Length);
}
array[index] = value;
}
}
public int Length
{
get { return array.Length; }
}
}
internal class MainClass
{
static void Main(string[] args)
{
MyList<string> str_list = new MyList<string>();
str_list[0] = "abc";
str_list[1] = "def";
str_list[2] = "ghi";
str_list[3] = "jkl";
str_list[4] = "mno";
for (int i = 0; i < str_list.Length; i++)
{
Console.WriteLine(str_list[i]);
}
Console.WriteLine();
MyList<int> int_list = new MyList<int>();
int_list[0] = 0;
int_list[1] = 1;
int_list[2] = 2;
int_list[3] = 3;
int_list[4] = 4;
for (int i = 0; i < int_list.Length; i++)
{
Console.WriteLine(int_list[i]);
}
}
}
형식 매개변수 제약시키기
T는 모든 데이터 형식을 대신할 수 있지만
특정 조건을 갖춘 형식에만 대응하는 형식 매개변수가 필요할 때도 있다.
이때 형식 매개변수의 조건을 줄 수 있다.
// 기본 형태
where 형식_매개변수 : 제약_조건
// 예시
// 클래스의 경우
class MyList<T> where T : MyClass {}
// 메소드의 경우
void CopyArray<T>(T[] source,T[] target) where T : struct
조건에 대한 설명은 다음과 같다
제약 조건 | 설명 |
---|---|
where T : struct | T는 값 형식이어야 한다. (int, char, float, double 등) |
where T : class | T는 참조 형식이어야 한다. (string, object, List 등) |
where T : new() | T는 매개변수가 없는 생성자를 가져야 한다. |
where T : 기반_클래스_이름 | T는 명시한 기반 클래스의 파생 클래스여야 한다. |
where T : 인터페이스_이름 | T는 지정된 인터페이스를 구현해야 한다. 인터페이스_이름에는 여러 개의 인터페이스를 명시할 수도 있다. |
where T : U | T는 또 다른 형식 매개변수 U 로부터 상속받은 클래스여야 한다. |
여기서 생소한 new와 U의 조건만 살펴보겠다
new()
인자를 한 개도 받지 않는 기본 생성자가 있는 경우에만 형식 매개변수로 할 수 있는 조건이다.
int 형은 기본 생성자가 있지만
string은 기본 생성자가 없기에 불가능하다.
다음은 클래스를 통한 예시이다.
class YesBasicConstructor
{
public YesBasicConstructor(){ } // 기본 생성자가 있음
}
class NoBasicConstructor
{
public NoBasicConstructor(int a) { } // 인자값을 하나 받는 생성자만 존재
}
void Main()
{
MyList<YesBasicConstructor> list = new MyList<YesBasicConstructor>(); // 정상
MyList<NoConstructor> list2 = new MyList<NoConstructor>(); // 에러 발생
}
U
where T : U
이것의 의미는 T가 U를 상속하는 조건이란 뜻이다.
좀 복잡한데 두 개의 형식 매개변수 사이의 관계가 상속과 피 상속인 것이다.
밑은 예시이다
class Base { }
class Derived : Base { }
class TUClass<T, U> where T : U // Base 또는 그의 자식클래스만 U로 받을 수 있음
{
T t;
U u;
public TUClass(T t, U u)
{
this.t = t;
this.u = u;
}
}
class MainApp
{
static void Main(string[] args)
{
Base baseClass = new Base();
Derived derivedClass = new Derived();
// 정상적으로 작동한다
TUClass<Derived, Base> tuClass = new TUClass<Derived, Base>(derivedClass, baseClass);
// 이 경우에는 Base가 Derived를 상속해주는 것이지 받는 관계는 아니므로 에러가 발생한다
//TUClass<Base, Derived> tuClass = new TUClass<Base, Derived>(baseClass, derivedClass);
}
}
일반화 컬렉션
이제 배운 컬렉션을 통해
일반화 컬렉션에 대해 살펴볼 수 있게 되었다.
이전에 얘기했던 컬렉션은 전부 object형식을 기반으로 하였고
그래서 다음과 같이 어떤 형식이든지 전부 담을 수 있었다.
ArrayList list = new ArrayList();
list.Add(10);
list.Add(5.0f);
list.Add("문자열");
이렇게 되면 잘못된 형식
을 담게 될 수도 있고,
object 형식에 기반하고 있기에 태생적으로 성능 문제
를 안고 있다.
컬렉션의 요소에 접근할 때마다 형식 변환이 주구장창 일어나기 때문이다.
일반화 컬렉션은 이 문제를 말끔히 해결한다.
아무 자료형이나 담을 수 있던 컬렉션을 한 가지 자료형으로 통일
하여
잘못 담을 가능성도, 성능 문제도 해결한다.
이번에 살펴볼 일반화 컬렉션은 네 가지이다.
List<T>
Queue<T>
Stack<T>
Dictionary<TKey, TValue>
이 네 가지는 앞에서 이미 배운 비 일반화 컬렉션과 거의 동일하므로
아주 간단하게 살펴보겠다.
List<T>
List
차이점이라면 List
한 컬렉션에 아무 자료형이나 담을 수 있던 ArrayList와 달리
형식 매개변수로 입력한 형식 외에는 입력을 허용하지 않는 것이다.
예시는 다음과 같다
static void Main()
{
List<int> int_list = new List<int>();
int_list.Add(1);
int_list.Add(2);
int_list.Add(3);
//int_list.Add(3f); 에러 발생
//int_list.Add("문자열"); 에러 발생
// 자료형이 명확하기에 foreach문에서 값을 담는 변수를 int형으로
// 사용할 수 있다.
foreach (int i in int_list)
{
Console.WriteLine(i);
}
}
Queue<T>
이 또한 위와 동일하다
static void Main()
{
Queue<int> queue = new Queue<int>();
queue.Enqueue(1);
queue.Enqueue(2);
//queue.Enqueue(3f); 에러 발생
while (queue.Count > 0)
{
Console.WriteLine(queue.Dequeue());
}
}
Stack<T>
이것도 같다..
static void Main()
{
Stack<int> queue = new Stack<int>();
queue.Push(1);
queue.Push(2);
//queue.Enqueue(3f); 에러 발생
while (queue.Count > 0)
{
Console.WriteLine(queue.Pop());
}
}
Dictionary<TKey, TValue>
Dioctionary는 Hashtable의 일반화 버전이다.
key의 자료형과 value 자료형을 통일해주는 것의 차이가 있다.
static void Main()
{
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("하나", 1);
dic["둘"] = 2;
Console.WriteLine(dic["하나"]);
Console.WriteLine(dic["둘"]);
}
foreach를 사용할 수 있는 일반화 클래스
저번에 foreach클래스를 IEnumerable 인터페이스를 상속해서 클래스를 만들었다.
일반화 클래스도 IEnumrable을 상속하면 일단은 foreach를 통해 순회할 수 있지만,
요소를 순회할 때마다 형식 변환을 수행하는 오버로드가 발생
한다는 문제점이 있음.
즉, 일반화를 통해 성능 문제를 해결해 놨는데 다시 성능문제에 부딪히게 된다.
이를위해 사용할 수 있는 방법은 IEnumrable의 일반화 버전인 IEnumrable<T>
다
이를 통해 성능 저하가 없는 foreach순회 가능한 클래스를 만들 수 있다.
IEnumrable<T>
과 IEnumrator<T>
는
IEnumrable와 IEnumrator을 각각 상속받아서 만들어 진 인터페이스기에
같은 함수지만 두 개씩 구현
해야 하는 경우가 있다.
메소드 GetEnumerator()
와 프로퍼티 Current
가 그렇다.
또한 IDisposable도 상속받았기에 Dispose
메소드도 구현되어야 한다
그 외에는 형식 매개변수 외에는 차이가 없으니 예시로만 보겠다.
다음 예시는 forecah가 되는 클래스를 만드는 예시이다
class MyList<T> : IEnumerable<T>, IEnumerator<T>
{
private T[] array;
int position = -1;
public MyList()
{
array = new T[3];
}
public T this[int index]
{
get { return array[index]; }
set
{
if (index >= array.Length)
{
Array.Resize<T>(ref array, index + 1);
Console.WriteLine($"배열이 리사이징됨 {array.Length}");
}
array[index] = value;
}
}
public int Length
{
get { return array.Length; }
}
public IEnumerator<T> GetEnumerator()
{
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
public T Current
{
get { return array[position];}
}
object IEnumerator.Current
{
get { return array[position]; }
}
public bool MoveNext()
{
if(position == array.Length - 1)
{
Reset();
return false;
}
position++;
return (position < array.Length);
}
public void Reset()
{
position = -1;
}
public void Dispose() { }
}
internal class Program
{
static void Main(string[] args)
{
MyList<string> str_list = new MyList<string>();
str_list[0] = "abc";
str_list[1] = "def";
str_list[2] = "ghi";
str_list[3] = "jkl";
str_list[4] = "mno";
foreach(string str in str_list)
{
Console.WriteLine(str);
}
}
}
1
댓글남기기