전체 페이지뷰

2016년 12월 23일 금요일

C# Generic

제네릭은 C# 2.0부터 CLR에 포함되었습니다. 형식 매개 변수라는 개념을 통해 하나 이상의 형식에 대한 메소드 등을 정의하고 인스턴스가 생성될 때에 형식을 정해주어, 불필요한 박싱을 피하고 런타임에 형식을 캐스팅하는 위험을 덜어주는 방법입니다.



이 방법을 통하면 형식마다 여러 함수를 오버로딩할 필요가 없으므로 프로그래머의 수고를 덜어주고 코드의 재사용성, 형식 안전성을 보장해 줍니다.

Generic Method


일반화 메소드의 경우부터 알아보겠습니다. 그 형식은 다음과 같습니다.

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

이것만 봐서는 감이 잡히지 않으므로 예를 들어 봅시다.

using System;
namespace GenericMethod
{
    class Program
    {
        static void Swap<T>(ref T lhs, ref T rhs)
        {
            T temp;
            temp = lhs;
            lhs = rhs;
            rhs = temp;
        }
        static void Main(string[] args)
        {
            int a = 1;
            int b = 2;
            Swap<int>(ref a, ref b);
            // <int>를 생략하여 컴파일러가 유추하게 해도 됩니다.
            Console.WriteLine(a + " " + b);
        }
    }
}
cs

Swap이라는 함수 뒤에 <T>가 있습니다. 이것이 바로 행식 매개변수입니다.
static void Swap<T>()라는 말은
static void int Swap()
static void float Swap()
static void double Swap()
static void string Swap()
...

의 모든 형식을 대표하는 이름입니다. 이런 제너릭 형식을 사용하여 형식에 구애받지 않는 메소드를 만들 수 있습니다.


Generic Class


일반화 메소드에서 형식을 T로 통일한 것과 마찬가지로 일반화 클래스도 형식을 T로 통일해서 표현합니다.

class 클래스명<T>
     //...; 
}
의 형식입니다.

using System;
namespace GenericClass
{
    class GenericArray<T>
    {
        private T[] array;
        public GenericArray(int index)
        {
            array = new T[index];
        }
        public T this[int index]
        {
            get
            {
                return array[index];
            }
            set
            {
                if (index >= array.Length)
                {
                    Array.Resize<T>(ref array, index + 1);
                    Console.WriteLine("배열 크기 변경 : {0}", array.Length);
                }
                array[index] = value;
            }
        }
        public int Length
        {
            get
            {
                return array.Length;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            GenericArray<int> intArray1 = new GenericArray<int>(5);
            GenericArray<double> intArray2 = new GenericArray<double>(5);
            
            for(int i = 0; i < 5; i++)
            {
                intArray1[i] = i;
                intArray2[i] = i + 5.5;
            }
            for(int i = 0; i < 5; i++)
            {
                Console.Write("{0} ", intArray1[i]);
            }
            Console.WriteLine();
            for (int i = 0; i < 5; i++)
            {
                Console.Write("{0} ", intArray2[i]);
            }
        }
    }
}
cs

조금 복잡한 예제입니다.
GenericArray라는 일반형식(T)의 클래스를 선언하였고 내부필드로 T[] 배열을 선언했습니다.
이 클래스에 인덱서를 도입하여 인덱싱을 가능하게 만들고,
메인 메소드에서  int형과 double형의 두 클래스를 만들어 값을 할당하고 출력해 보았습니다.

형식매개변수에 대한 제약조건


모든 형식에 대한 매개변수가 아닌 특정 조건을 가진 형식에만 대응하는 매개변수를 만드는 방법도 있습니다. "where" 키워드를 사용해서 이런 조건을 만드는데, 형식은 다음과 같습니다.

class MyList<T> whrere T :MyClass
{
    //
}

바로 "where T : 제약조건"의 구문을 추가하여 이러한 제약조건을 걸 수 있습니다.
올 수 있는 제약조건은 아래와 같습니다.
제약 조건설명
where T: struct형식 인수가 값 형식이어야 합니다. Nullable를 제외한 임의의 값 형식을 지정가능 합니다. 
where T : class형식 인수가 참조 형식이어야 합니다. 이는 모든 클래스, 인터페이스, 대리자 또는 배열 형식에도 적용됩니다.
where T : new()형식 인수가 매개 변수 없는 공용 생성자여야 합니다. 다른 제약 조건과 함께 사용하는 경우 new() 제약 조건은 마지막에 지정해야 합니다.
where T : <기본 클래스 이름>형식 인수가 지정된 기본 클래스이거나 지정된 기본 클래스에서 파생되어야 합니다.
where T : <인터페이스 이름>형식 인수가 지정된 인터페이스이거나 지정된 인터페이스를 구현해야 합니다. 여러 인터페이스 제약 조건을 지정할 수 있습니다. 제한하는 인터페이스는 제네릭이 될 수도 있습니다.
where T : UT에 대해 지정한 형식 인수가 U에 대해 지정한 인수이거나 이 인수에서 파생되어야 합니다.
예제를 들어 사용법을 설명해 보겠습니다.

namespace ConstraintsOnTypeParameters
{
    // struct형의 클래스 정의
    class StructArray<T> where T :struct
    {
        public T[] Array { get; set; }
        public StructArray(int size)
        {
            Array = new T[size];
        }
    }
    // class 형의 클래스 정의
    class RefArray<T> where T :class
    {
        public T[] Array { get; set; }
        public RefArray(int size)
        {
            Array = new T[size];
        }
    }
    // 파생 클래스형의 클래스 정의
    class Base { }
    class Derived :Base { }
    class BaseArray<U> where U :Base
    {
        public U[] Array { get; set; }
        public BaseArray(int size)
        {
            Array = new U[size];
        }
        public void CopyArray<T>(T[] Source) where T : U
        {
            Source.CopyTo(Array, 0);
        }
    }
    // 인터페이스 형의 클래스 정의
    public interface IPrint
    {
        void print();
    }
    public class MessagePrinter :IPrint
    {
        string Message;
        public MessagePrinter(string message)
        {
            this.Message = message;
        }
        public void print()
        {
            Console.WriteLine(this.Message);
        }
    }
    class InterfaceArray<T> where T : IPrint
    {
        public T[] Array { get; set; }
        public InterfaceArray(int size)
        {
            Array = new T[3];
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            // int형의 StructArray class 생성
            StructArray<int> a = new StructArray<int>(3);
            a.Array[0= 0;
            a.Array[1= 1;
            a.Array[2= 2;
            for (int i = 0; i < 3; i++)            
                Console.Write("{0} ", a.Array[i]);
            Console.WriteLine();
            // double형의 StructArray를 형식으로 갖는 RefArray 생성
            RefArray<StructArray<double>> b = new RefArray<StructArray<double>>(3);
            b.Array[0= new StructArray<double>(5);
            b.Array[1= new StructArray<double>(10);
            b.Array[2= new StructArray<double>(1005);
            
            // Base형의 BaseArray 생성
            BaseArray<Base> c = new BaseArray<Base>(3);
            c.Array[0= new Base();
            c.Array[1= new Derived();
            c.Array[2= System.Activator.CreateInstance<Base>();
            // Derived형의 BaseArray 생성
            BaseArray<Derived> d = new BaseArray<Derived>(3);
            d.Array[0= new Derived();
            d.Array[1= System.Activator.CreateInstance<Derived>();
            d.Array[2= System.Activator.CreateInstance<Derived>();
            BaseArray<Derived> e = new BaseArray<Derived>(3);
            e.CopyArray<Derived>(d.Array);
            // interface 형의 InterfaceArray 생성
            InterfaceArray<MessagePrinter> f = new InterfaceArray<MessagePrinter>(3);
            f.Array[0= new MessagePrinter("첫번째");
            f.Array[1= new MessagePrinter("두번째");
            f.Array[2= new MessagePrinter("세번째");
            for (int i = 0; i < 3; i++)
                 f.Array[i].print();
            Console.WriteLine();
        }
    }
}
cs



Generic Collection


전에 살펴본 컬렉션은  모두 object 형에 대한 것이었습니다. 그래서 박싱과 언박싱이 게속 일어나야 하므로 성능의 문제를 가져올 수 있다고 했습니다. 그러나 제네릭을 이용하면 그런 위험 없이 컬렉션을 사용할 수 있습니다.

Systme.Collections.Generic 에 다양한 컬렉션들이 정의되어 사용 가능합니다.(참고)
여기서는 그 중 List, Queue, Stack, Dictionary에 대해 알아보고자 합니다.

List<T>

object의 컬렉션인 ArrayList와 같은 기능을 가지고 있습니다.
바로 예제를 통해 사용법을 살펴 봅시다.
using System;
using System.Collections.Generic;
namespace GenericCollections
    class MainApp
    {
        static void Main(string[] args)
        {
            List<int> list = new List<int>();
            for (int i = 0; i < 5; i++)
            list.Add(i+1);
            foreach (int e in list)
                Console.Write("{0} ", e);
            Console.WriteLine();
            list.RemoveAt(2);
            foreach (int e in list)
                Console.Write("{0} ", e);
            Console.WriteLine();
            list.Insert(23);
            foreach (int e in list)
                Console.Write("{0} ", e);
            Console.WriteLine();
        }
    
    }
}
cs


Queue<T>, Stack<T>

object 형의 Queue와 동일한 사용법을 가지고 있습니다.

Queue<int> queue = new Queue<int>();
Stack<int> stack = new Stack<int>();

와 같이 생성하여 사용합니다.

Dictionary<TKey, TValue>

역시 Hashtable과 같은 역할을 합니다. 키와 밸류에 대해 각각 형식매개변수를 요구합니다.

Dictionary<int, string> dic = new Dictionary<int, string>();

dic[1]="하나";
...

과 같이 사용합니다.


Generic 에서 foreach 사용하기

일반화 클래스에서 IEnumerable, IEnumerator로 foreach를 이용해 요소들을 순회할 수 있는 것처럼 IEnumerable<T>, IEnumerator<T> 를 이용해 형식 변환 없이 froeach 순회 가능한 클래스를 작성할 수 있게 됩니다.

IEnumerable<T> 에서 구현해야할 것은
IEnumerator GetEnumerator()메소드와 IEnumerator<T> GetEnumerator()메소드 두개 입니다. 비슷하지만 반환형식이 다르므로 둘 다 구현해야 합니다.

그리고, iEnumerator<T>에서는
메소드로 MoveNext(), Reset(), Dispose() 과,
속성으로 Object Current, T Current를 구현해야 합니다.

좀 길어지겠지만 둘을 구현한 예를 들어 보이겠습니다. 전에 컬렉션에서 foreach 구현하던 것과 유사합니다.

using System;
using System.Collections;
using System.Collections.Generic;
namespace EnumerableGeneric
    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("배열 크기 변화 : {0}", array.Length);
                }
                array[index] = value;
            }
        }
        public int Length
        {
            get { return array.Length; }
        }
        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < array.Length; i++)
            {
                yield return (array[i]);
            }
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            for (int i = 0; i < array.Length; i++)
            {
                yield return (array[i]);
            }
        }
        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()
        {
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            MyList<string> stringList = new MyList<string>();
            stringList[0= "abc";
            stringList[1= "def";
            stringList[2= "ghi";
            stringList[3= "jkl";
            stringList[4= "mno";
            foreach (string str in stringList)
                Console.WriteLine(str);
        }
    
    }
}
cs

댓글 없음:

댓글 쓰기