본문 바로가기

C#/이것이 C#이다

[ 이것이 C#이다 ] Chapter 11 . 일반화 프로그래밍

[ 학습 흐름 ]

[ 학습 흐름 ]

  1. 일반화 프로그래밍
  2. 일반화 메소드
  3. 일반화 클래스
  4. 형식 매개변수 제약시키기
  5. 일반화 컬렉션
  6. foreach를 사용할 수 있는 일반화 클래스

[ 일반화 프로그래밍 ]

[ 일반화 ]

- 특수한 개념으로 공통된 개념을 찾아 묶는것을 일반화라고 한다 ex) 포유류

- 일반화 프로그래밍은 일반화 하는 대상이 데이터 형식이다 .

- 매개변수의 형식만 다를 뿐, 내부 논리는 똑같은 코드를 일반화 할 수 있다면 여러 버전의 오버로딩이 필요 없다 .


[ 일반화 메소드 ]

[ 일반화 메소드 ]

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

- 형식 자리에 구체적 이름대신 형식 매개변수인 T가 들어간다 .

- 메서드 뒤에 형식 매개변수를 <T>의 형태로 입력해줘야 한다 .

- 호출시 T 대신 형식 이름을 입력하면 컴파일러가 메소드 나머지 부분의 T를 형식 매개변수 값으로 치환한다 .

using System;
using System.Collections;

namespace CopyingList;


class MainApp
{
    static void CopyingArray<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];

        CopyingArray<int>(source, target);

        foreach(int element in target)
            Console.WriteLine(element);

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

        CopyingArray<string>(source2, target2);

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

[ 일반화 클래스 ]

[ 일반화 클래스 ]

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

Array_Genenric<int>intArr = new Array_Generic<int>();
Array_Genenric<double>dbArr = new Array_Generic<double>();

- 데이터 형식을 일반화한 클래스

- 기능이 같지만 내부적으로 사용하는 데이터 형식만이 다른 경우 일반화 할 수 있다 .

using System;
using System.Collections;

namespace Generic;


class MainApp
{
    class MyList<T>
    {
        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 Resized : {array.Length}");
                }
                array[index] = value;
            }
        }
        public int Length
        {
            get { return array.Length; }
        }
    }
    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] = "jki";
        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]);

    }
}

[ 형식 매개변수 제약시키기 ]

[ 형식 매개변수의 제약 ]

- 일반화 메소드/클래스의 경우 형식 매개변수는 모든 데이터 형식을 대신할 수 있다 .

- 종종 특정 조건을 갖춘 형식에만 대응하는 형식 매개변수가 필요 할 수 있다 .

- where 절과 함께 사용할 수 있는 제약 조건은 아래와 같다 .

제약 설명
where T:struct T는 값 형식이어야 한다 .
where T:class T는 참조형식이어야 한다 .
where T:new() T는 반드시 매개변수가 없는 생성자가 있어야 한다 .
where T:기반 클래스 이름 T는 명시한 기반 클래스의 파생 클래스이어야 한다 .
where T: 인터페이스 이름 T는 명시한 인터페이스를 반드시 구현해야 한다 .복수도 가능
where T:U T는 또다른 형식 매개변수 U로부터 상속받은 클래스여야 한다
using System;
using System.Collections;
using System.Dynamic;

namespace ConstraintsOnTypeParameters;

class StructArray<T> where T:struct
{
    public T[]Array { get; set; }
    public StructArray(int size)
    {
        Array = new T[size];
    }
}

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);
    }
    
}

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

        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();
        //d.Array[1] = new Base();  Base 형식은 할당 할 수 없다
        d.Array[1]= CreateInstance<Derived>();

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

[ 일반화 컬렉션 ]

[ 일반화 컬렉션 ]

- 컬렉션 클래스들은 object 형식 기반이기에 성능문제가 존재했다 . (컬렉션 요소에 접근시마다 형식변환이 일어나기에)

- 일반화 컬렉션은 컴파일시 컬렉션에서 사용할 형식이 결정 , 형식변환을 일으키지 않고 잘못된 객체를 담지도 않는다 .


[ List<T> ]

- ArrayList와 같은 기능을 한다 .

using System;
using System.Collections;
using System.Collections.Generic;

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

        foreach(int i in list) 
            Console.WriteLine(i);
        Console.WriteLine();

        list.RemoveAt(2);
        foreach (int i in list)
            Console.WriteLine(i);
        Console.WriteLine();

        list.Insert(2, 2);
        foreach (int i in list)
            Console.WriteLine(i);
        Console.WriteLine();
    }
}

[ Queue<T> ]

- Queue와 같은 기능을 한다 .

using System;
using System.Collections;
using System.Collections.Generic;

namespace UsingGenericList;
class MainApp
{   
    static void Main(string[] args)
    {
       Queue<int>queue= new Queue<int>();
        queue.Enqueue(1);
        queue.Enqueue(2);
        queue.Enqueue(3);
        queue.Enqueue(4);
        queue.Enqueue(5);

        while(queue.Count > 0)
        Console.WriteLine(queue.Dequeue());
    }
}

[ Stack<T> ]

- Stack과 같은 기능을 한다 .

using System;
using System.Collections;
using System.Collections.Generic;

namespace UsingGenericList;
class MainApp
{   
    static void Main(string[] args)
    {
       Stack<int>stack= new Stack<int>();
        stack.Push(1);
        stack.Push(2);
        stack.Push(3);
        stack.Push(4);
        stack.Push(5);

        while(stack.Count > 0)
        Console.WriteLine(stack.Pop());
    }
}

[ Dictionary<TKey,TValue> ]

- HashTable의 일반화 버전.키와 밸류 두개의 형식 매개변수를 요구한다 .

using System;
using System.Collections;
using System.Collections.Generic;

namespace UsingDictionary;
class MainApp
{   
    static void Main(string[] args)
    {
        Dictionary<string, string> dic = new Dictionary<string, string>();
        dic["하나"] = "One";
        dic["둘"] = "Two";
        dic["셋"] = "Three";

        Console.WriteLine(dic["하나"]);
        Console.WriteLine(dic["둘"]);
        Console.WriteLine(dic["셋"]);
    }
}

[ foreach를 사용하는 일반화 클래스 ]

[ foreach를 사용할수 있는 일반화 클래스 ]

- 일반화 클래스도 IEnumerable 인터페이스를 상속하면 foreach를 통해 순회 할 수 있지만 

요소를 순회할때마다 형식변환을 수행하는 오버로드가 발생한다 .

-이를 위해 IEnumerable<T>인터페이스가 존재한다 .


[ IEnumerable<T> ]

메소드 설명
IEnumerator GetEnumerator() IEnumerator 형식의 객체를 반환(IEnumerable로부터 상속받은 메소드)
IEnumerator<T> GetEnumerator() IEnumerator<T>형식의 객체를 반환

- 형식 변환으로 인한 성능저하가 없으면서도 foreach 순회가 가능한 클래스를 작성 가능하다 .

- 또한 해당 객체는 IEnumerator <T>형식의 객체를 반환하는 GetEnumerator()를 구현해야 한다 .


[ IEnumerator<T> ]

메소드 설명
boolean MoveNext() 다음 요소로 이동한다 . 컬렉션의 끝을 지나면 false 이동이 성공하면 true를 반환한다
void Reset() 컬렉션 첫번쨰 위치의 앞으로 이동한다 .
Object Current{get;} 컬렉션의 현재 요소를 반환한다 .
T Current{get;} 켤렉션의 현재 요소를 반환한다 .
using System;
using System.Collections;
using System.Collections.Generic;

namespace EnumerableGeneric;

class MyList<T> : IEnumerable<T>, IEnumerator<T>
{
    T[] array;
    int position = -1;

    public MyList() { array = new T[3]; }
    public T this[int index]
    {
        get { return array[index]; }
        set
        {
            if (array.Length <= index)
            {
                Array.Resize(ref array, index + 1);
                Console.WriteLine($"Array Resized : {array.Length}");
            }
            array[index] = value;
        }

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

    #region IEnumerable Method
    public IEnumerator<T> GetEnumerator()
    {
        Console.WriteLine("GetEnumerator T");
        return this;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        Console.WriteLine("GetEnumerator");
        return this;
    }
    #endregion

    #region IEnumerator Method
    public T Current { get {
            Console.WriteLine("Current T");
            return array[position]; 
        } }

    object IEnumerator.Current { get {
            Console.WriteLine("Current");
            return array[position]; } }

    public void Dispose()
    {
       
    }

    public bool MoveNext()
    {
        Console.WriteLine("Move Next");

        if (position==array.Length-1)
        {
            Reset();
            return false;
        }    
        position++;
        Console.WriteLine($"현재 길이는{ position < array.Length}");
        return (position<array.Length);
    }

    public void Reset()
    {
        Console.WriteLine("Reset");

        position = -1;
    }
    #endregion

}

class MainApp
{   
    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);
        }
        Console.WriteLine();

        MyList<int>int_list= new MyList<int>();
        int_list[0] = 1;
        int_list[1] = 2;
        int_list[2] = 3;
        int_list[3] = 4;
        int_list[4] = 5;

        foreach(int i in int_list)
            Console.WriteLine(i);
    }
}