본문 바로가기

C# 공부/C# 기본 문법

일반화(Generic) 프로그래밍 (일반화 메소드/클래스, 형식매개변수 제약, 일반화 컬렉션)

A. 일반화 메소드(Generic Method)

데이터 형식을 일반화한 메소드. 일반화할 형식이 들어갈 자리에 구체적인 형식의 이름 대신 형식 매개 변수(Type Parameter)가 들어간다.

//int형식 
void CopyArray(int[] source, int[] target)
{
    for(int i=0;i<source.Length;i++)
        target[i]=source[i];
}

//string형식
void CopyArray(string[] source, string[] target)
{
    for(int i=0;i<source.Length;i++)
        target[i]=source[i];
}

위 코드에서 데이터형식이 사용된 부분을 T (Type) 기호로 치환하면 아래와 같다.

void CopyArray(T[] source, T[] target)
{
    for(int i=0;i<source.Length;i++)
        target[i]=source[i];
}

위 코드에서 T는 C#에서 지원하는 형식이 아니므로, 메소드 이름뒤에 <과 > 를 넣고 사이에 T를 넣으면 T는 "형식매개변수(Type Parameter)가 된다.

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[] source = { 1, 2, 3, 4, 5 };
    int[] target = new int[source.Length];

    CopyArray<int>(source, target);

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

    string[] source2 = { "a", "b", "c", "d" };
    string[] target2 = new string[source2.Length];

    CopyArray<string>(source2, target2);

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

 

B. 일반화 클래스

마찬가지로 데이터 형식을 일반화한 클래스. 

class Array_Int
{
    private int[] array;
    public int GetElment(int index){return array[index];}
}

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

//위의 두 클래스를 일반화 클래스로 만들면
class Array_Generic<T>
{
	private T[] array;
    public T GetElement(int index){return array[index];}
}

//일반화 클래스 사용시?
Array_Generic<int> intArr = new Array_Generic<int>();
Array_Generic<double> dblArr = new Array_Generic<double>();

 

C. 형식 매개 변수 제약시키기

일반화 메소드나 일반화 클래스가 입력받는 형식매개변수 T는 모든 데이터형식을 대신할수 있었지만, 특정 조건을 갖춘 형식에만 대응하도록 형식 매개변수의 조건에 제약을 줄 수 있다.

예를들어 MyList<T> 클래스의 형식매개변수 T에 'MyClass로 부터 상속받는 형식이어야 한다.' 라는 제약을 주고싶으면 선언문의 헤더에 where절을 추가해주면된다. 

class MyList<T> where T : MyClass // where 형식 매개 변수 : 제약조건
{
    //---
}

 

* where절과 함께 사용 할 수 있는 제약 조건

제약 설명
where T : struct T는 값 형식이어야 한다.
where T : class T는 참조 형식이어야 한다.
where T : new() T는 반드시 매개 변수가 없는 생성자가 있어야 한다.
where T : 기반클래스 이름 T는 명시한 기반 클래스의 파생 클래스여야 한다.
where T : 인터페이스 이름 T는 명시한 인터페이스를 반드시 구현해야한다.
where T : U T는 또 다른 형식 매개 변수 U로부터 상속받은 클래스여야한다.
 class StructArray<T> where T : struct //T는 값 형식이어야한다.
 {
     public T[] Array { get; set; }
     public StructArray(int size)
     {
         Array = new T[size];
     }
 }

 class RefArray<T> where T : class //T는 참조 형식이어야한다.
 {
     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);
     }
 }

 class MainApp
 {
     public static T CreateInstance<T>() where T : new()
     {
         return new T();
     }
     static void Main(string[] args)
     {
         StructArray<int> a = new StructArray<int>(3);
         a.Array[0] = 0;
         a.Array[1] = 1;
         a.Array[2] = 2;

         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>(100);

         BaseArray<Base> c = new BaseArray<Base>(3);
         c.Array[0] = new Base();
         c.Array[1] = new Derived();
         c.Array[2] = CreateInstance<Base>();

         BaseArray<Derived> d = new BaseArray<Derived>(3);
         d.Array[0] = new Derived(); //Base 형식은 여기 할당할 수 없다.
         d.Array[1] = CreateInstance<Derived>();
         d.Array[2] = CreateInstance<Derived>();

         BaseArray<Derived> e = new BaseArray<Derived>(3);
         e.CopyArray<Derived>(d.Array);
     }
 }

 

D. 일반화 컬렉션

컬렉션 클래스들 (ArrayList,Queue,Stack,Hash Table 등) 은 object형식(기본/복합 데이터 형식, 값/참조 형식)을 기반으로 했었으므로 컬렉션 객체에 int, string, MyClass 객체를 담을수있었다.

따라서 object기반의 컬렉션은 컬렉션 요소에 접그할때마다 형식변환이 일어나므로 성능문제가있다.

성능 문제를 개선할 방법으로는 일반화(Generic) 컬렉션을 사용하게되면 쓸데없는 형식변환을 일으키지 않게되며, 잘못된 형식의 객체를 담게될 위험을 피할 수 있다.

일반화 컬렉션을 모아놓은 System.Collections.Generic 네임스페이스의 대표적인 클래스 4가지는 다음과같다.

 

1. List<T>

비 일반화 클래스인 ArrayList와 동일한 기능을하며, T형식외에는 입력이 불가능하다는것이 차이점이다.

  static void Main(string[] args)
  {
      List<int> list = new List<int>();
      for (int i = 0; i < 5; i++)
          list.Add(i);

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

      list.RemoveAt(2);
      list.Insert(2, 2);
  }

 

2. Queue<T>

비 일반화 클래스인 Queue와 동일한 기능을하며, 마찬가지로 T형식외에는 입력이 불가하다.

static void Main(string[] args)
{
    Queue<int> queue = new Queue<int>();
    queue.Enqueue(1);
    queue.Enqueue(2);
    queue.Enqueue(3);
    while (queue.Count > 0)
        Console.WriteLine(queue.Dequeue());
}

 

3. Stack<T>

비 일반화 클래스인 Stack와 동일한 기능을하며, 마찬가지로 T형식외에는 입력이 불가하다.

static void Main(string[] args)
{
    Stack<int> stack = new Stack<int>();
    stack.Push(1);
    stack.Push(2);
    stack.Push(3);
    while (stack.Count > 0)
        Console.WriteLine(stack.Pop());
}

 

4. Dictionary<TKey,TValue>

비 일반화 클래스인 Hash Table과 동일한 기능을하며, 마찬가지로 T형식외에는 입력이 불가하다.

static void Main(string[] args)
{
    Dictionary<string, string> dic = new Dictionary<string, string>();
    dic["one"] = "하나";
    dic["two"] = "둘";
    dic["three"] = "셋";

    Console.WriteLine(dic["one"]);
    Console.WriteLine(dic["two"]);
    Console.WriteLine(dic["three"]);
}