Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C#] 일반화 프로그래밍 (Generic Programming) 본문

Programming/C#

[Programming/C#] 일반화 프로그래밍 (Generic Programming)

scii 2020. 9. 18. 03:09
C++ 언어에서의 "템플릿 (Template)" 이라고 생각하면 된다. template <typename T>

C#은 프로그래머가 작성한 하나의 코드가 여러 가지 데이터 형식에 맞춰 동작할 수 있도록 "일반환 프로그래밍" 을 지원한다.
일반환 프로그래밍은 코드 생산성을 좌우하는 아주 중요한 요소이며 일반화 컬렉션은 반드시 익혀둘 필요가 충분하다.

특수한 개념으로부터 공통된 개념을 찾아 묶는 것을 "일반화 (Generalization)" 라고 한다. "일반화 프로그래밍 (Generic Programming) " 은 이러한 일반화를 이용하는 프로그래밍 기법이다.

일반화하는 대상은 "데이터 형식 (Data Type)" 이다.
void CopyArray( int[] source, int[] target)
{
    for(int i=0; i<source.Length; i++){
        target[i] = source[i];
    }
}

이러한 1차원 배열을 복사하는 메소드를 작성했다고 가정해보자. 
만약 문자열 배열을 복사하는 기능이 필요하다면 CopyArray() 메소드를 문자열 매개변수를 받는 메소드 오버로딩을 작성해야 한다. 그런데 문자열 말고도 실수형, MyClass 형식의 배열을 복사해야 하는 상황이 오면 골치가 아파진다.
곰곰히 생각해보자. 배열의 형식만 다를 뿐, 내부 논리는 똑같다. 그렇다면 특수한 형식을 사용하는 코드를 일반화한다면 CopyArray() 메소드를 오버로딩하지 않고도 모든 형식을 지원할 수 있지 않을까? 
일반화 프로그래밍은 바로 이런 아이디어를 바탕으로 만들어진 프로그래밍 패러다임이다.


일반화 메소드

"일반화 메소드 (Generic Method)" 는 이름처럼 데이터 형식을 일반화한 메소드이다. 
일반화 메소드의 선언 문법은 일반 메소드의 선언 문법과 대부분 비슷하다. 다만 일반화할 형식이 들어갈 자리에 구체적인 형식의 이름 대신 "형식 매개 변수 (Type Parameter)" 가 들어간다는 것이 다르다.

한정자 반환형식 메소드이름<형식 매개 변수> (매개 변수 목록)
{
    // ...
}

// CopyArray() 메소드를 일반화한 모습
void CopyArray<T> (T[] source, T[] target)
{
    for(int i=0; i<source.Length; i++)
    {
        target[i] = source[i];
    }
}

// 형식 매개 변수 T에 int를 대입.
CopyArray<int>(source, target);
T 는 형식 (Type) 을 뜻한다.

T가 C#이 지원하는 형식이 아님을 알고 있다. 그래서 컴파일하면 에러가 발생한다. 그렇기때문에 형식을 매개 변수로 넘겨줘야 한다.
메소드 이름 뒤에 꺽쇠 괄호 <와 >를 넣어주고 그 사이에 T를 넣으면 T는 "형식 매개 변수 (Type Parameter)" 가 된다. CopyArray()를 호출할 때 < > 사이의 T 대신 형식의 이름을 입력해주면 컴파일러는 메소드의 나머지 부분에 대해서도 T를 형식 매개 변수 값으로 치환한다.
이런식으로 일반화를 하게 된다면, CopyArray()는 어떤 형식의 배열이든 복사할 수 있다.

using System;

namespace test
{
    internal class Program
    {
        private static void CopyArray<T>(T[] source, T[] target)
        {
            for (int i = 0; i < source.Length; i++)
            {
                target[i] = source[i];
            }
        }

        public static void Main(string[] args)
        {
            int[] source = new int[] {1, 2, 3, 4, 5};
            int[] target = new int[source.Length];
            
            CopyArray<int>(source, target);

            foreach (int element in target)
            {
                Console.Write($"{element} ");
            }
            Console.WriteLine();

            string[] source2 = {"하나", "둘", "셋", "넷", "다섯"};
            string[] target2 = new string[source2.Length];
            
            CopyArray<string>(source2, target2);

            foreach (string element in target2)
            {
                Console.Write($"{element} ");
            }
            Console.WriteLine();
        }
    }
}


/* 결과

1 2 3 4 5 
하나 둘 셋 넷 다섯 

*/

일반화 클래스

일반화 클래스는 데이터 형식을 일반화한 클래스이다. 일반화 클래스를 선언하는 문법은 다음과 같다.

class 클래스이름 <형식 매개 변수>
{
    // ...
}

일반화 메소드가 그랬던 것처럼, 일반화 클래스도 형식 매개 변수가 있는 것을 제외하면 보통의 클래스와 똑같다.

예를 들어 아래와 같은 클래스가 있다고 가정해보자. ArrayInt와 ArrayDouble은 똑같은 기능을 하는 클래스이지만, 내부적으로 사용하는 데이터 형식이 다르기 때문에 두 개의 클래스로 분리하여 구현해야 한다.

class ArrayInt
{
    private int[] array;
    public int GetElement(int index) { return array[index]; }
}

class ArrayDouble
{
    private double[] array;
    public double GetElement(int index) { return array[index]; }
}

이 두 클래스는 일반화가 가능하다. 데이터 형식을 제외하면 다른 부분들은 서로 동일하기 때문이다.
다음은 형식 매개 변수를 이용하여 위의 코드를 "일반화 클래스" 로 개선한 예이다.

class ArrayGeneric<T>
{
    private T[] array;
    public T GetElement(int index) { return array[index]; }
}

ArrayGeniric 클래스의 형식 매개 변수 T 는 객체를 생성할 때 입력받은 형식으로 치환되어 컴파일된다.

일반화 클래스의 예제

using System;
using System.Collections;

namespace test
{
    class MyList<T> : IEnumerable, IEnumerator
    {
        private T[] array;
        private 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, array.Length + 1);
                    Console.WriteLine($"Array Resizing: {array.Length}");
                }

                array[index] = value;
            }
        }

        public int Length
        {
            get { return array.Length; }
        }

        public void Reset()
        {
            position = -1;
        }

        public bool MoveNext()
        {
            if (position == (array.Length - 1))
            {
                Reset();
                return false;
            }

            position++;
            return (position < array.Length);
        }

        public object Current
        {
            get { return array[position]; }
        }

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < array.Length; i++)
            {
                yield return array[i];
            }
        }
    }

    internal class Program
    {
        public 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 (var ele in str_list)
            {
                Console.Write($"{ele,-10} ");
            }
            Console.WriteLine();

            MyList<int> int_list = new MyList<int>();
            for (int i = 0; i < 5; i++)
            {
                int_list[i] = i * 10;
            }

            foreach (var ele in int_list)
            {
                Console.Write($"{ele,-10} ");
            }

            Console.WriteLine();
        }
    }
}


/* 결과

Array Resizing: 4
Array Resizing: 5
abc        def        ghi        jkl        mno        
Array Resizing: 4
Array Resizing: 5
0          10         20         30         40         

*/
Comments