6 분 소요

이 글은 한빛미디어의 이것이 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 클래스는 비 일반화 클래스인 ArrayList와 같은 기능을 하며 사용법 역시 동일하다.

차이점이라면 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

태그:

카테고리:

업데이트:

댓글남기기