본문 바로가기

C#/이것이 C#이다

11장. 일반화 프로그래밍

반응형

1. 일반화 프로그래밍

일반화: 특수한 개념으로부터 공통된 개념을 찾아 묶는 것

일반화 프로그래밍: 데이터 형식을 일반화하는 것

2. 일반화 메소드

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

제네릭 기본

static void Main(string[] args)
{
    int[] arr = { 1, 2, 3 };
    Console.WriteLine(First(arr));
}

static T First<T>(T[] arr)
{
    return arr[0];
}
class Program
{
    // 정적 메소드로 만들지 않으면 객체.함수명으로 호출해야 함
    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 = { "하나", "둘", "셋", "넷", "다섯" };
        string[] target2 = new string[source2.Length];

        CopyArray<string>(source2, target2);

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

3. 일반화 클래스

제네릭 클래스

class Program
{
    static void Main(string[] args)
    {
        var range = new Range<int>(1, 2);
        var range2 = new Range<char>('a', 'z');
    }
}

class Range<T>
{
    T Start;
    T End;

    public Range(T start, T end)
    {
        Start = start;
        End = end;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Array_Generic<int> intArr = new Array_Generic<int>();
        // Array_Int intArr = new Array_Int(); // 와 동일
        Array_Generic<double> doubleArr = new Array_Generic<double>();
        // Array_Double doubleArr = new Array_Double(); // 와 동일
    }
}

#region [1. 일반화 클래스 X - 똑같은 기능을 하는 두 클래스]
class Array_Int
{
    private int[] array;
    // ...
    public int GetElement(int index) { return array[index];  }
}

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

#region [2. 일반화 클래스]
class Array_Generic<T>
{
    private T[] array;
    // ...
    public T GetElement(int index) { return array[index]; }
}
#endregion

활용 예제

class MyList<T> // 가변길이 배열
{
    private T[] array;

    // 생성자: 길이가 3인 새로운 배열 생성
    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); // Array를 리사이즈
                Console.WriteLine($"Array Resized: {array.Length}");
            }
            array[index] = value;
        }
    }

    public int Length { get { return array.Length; } }
}
class Program
{
    static void Main(string[] args)
    {
        MyList<string> str_list = new MyList<string>();
        str_list[0] = "adb";
        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]);
        }
        Console.WriteLine();
    }
}

4. 형식 매개 변수 제약하기

제약 설명
where T : struct T는 값 형식
where T : class T는 참조 형식
where T : new() T는 매개변수가 없는 생성자 존재
where T : 클래스명 T는 해당 클래스의 파생 클래스
where T : 인터페이스명 T는 해당 인터페이스 구현
where T : U T는 다른 형식 매개 변수 U의 파생
static void Main(string[] args)
{
    int[] a = { 1, 2, 3 };
    string[] b = { "1", "2", "3" };
    //First(a); //Error
    First(b);
}

static T First<T>(T[] arr) where T: class
{
    return arr[0];
}
 interface iClass
{
    // ...
}

class MyClass
{
    // ...
}

class MyList<T> where T : MyClass // Type 제한: MyClass로부터 상속받는 형식이어야 할 것
{
    // ...
}

class MyList2<T> where T : iClass // Type 제한: iClass를 반드시 구현해야 할 것
{
    // ...
}

class MyClass<T> where T : class // Type 제한: 참조 형식이어야 할 것
{
    // ...
}

class Program
{
    static void CopyArray<T>(T[] source, T[] target) where T : struct // Type 제한: 값 형식이어야 할 것
    {
        for (int i = 0; i < source.Length; i++) {
            target[i] = source[i];
        }
    }

    static void CopyArray<T>() where T : new() // Type 제한: 반드시 매개 변수가 없는 생성자가 있어야 할 것
    {

    }

    static void Main(string[] args)
    {

    }
}

활용 예제

class StructArray<T> where T : struct // T는 값 형식일 것
{
    public T[] Array { get; set; }
    public StructArray(int size) // 생성자: size 크기의 배열을 생성
    {
        Array = new T[size];
    }
}

class RefArray<T> where T : class // T는 참조 형식일 것
{
    public T[] Array { get; set; }
    public RefArray(int size) // 생성자: size 크기의 배열을 생성
    {
        Array = new T[size];
    }
}

class Base { } // Base 클래스
class Derived : Base { } // Base 클래스를 상속받은 Derived 클래스
class BaseArray<U> where U : Base // U는 Base 클래스의 파생 클래스일 것
{
    public U[] Array { get; set; }
    public BaseArray(int size) // 생성자: size 크기의 배열을 생성
    {
        Array = new U[size];
    }

    public void CopyArray<T>(T[] Source) where T : U // T는 U를 상속받은 형식일 것
    {
        // Source의 모든 요소를 인덱스 0부터 시작하여 Array에 복사
        Source.CopyTo(Array, 0);
    }
}

class Program
{
    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(); // Base 형식은 여기에 할당할 수 없다.
        d.Array[1] = CreateInstance<Derived>();
        d.Array[2] = CreateInstance<Derived>();

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

5. 일반화 컬렉션

List<T>

class Program
{
   static void Main(string[] args)
    {
        // List<T>: ArrayList와 달리 형식 매개 변수 T 외에는 입력을 허용하지 않음
        List<int> list = new List<int>();
        for (int i = 0; i < 5; i++)
            list.Add(i);

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

        list.RemoveAt(2);

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

        list.Insert(2, 2);

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

Queue<T>

class Program
{
   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>

class Program
{
   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());
        }
    }
}
class Program
{
   static void Main(string[] args)
    {
        Dictionary<string, string> dic = new Dictionary<string, string>();

        dic["하나"] = "one";
        dic["둘"]   = "two";
        dic["셋"]   = "three";
        dic["넷"]   = "four";
        dic["다섯"] = "five";

        foreach (var item in dic.Keys) {
            Console.WriteLine(dic[item]);
        }
    }
}

6. foreach를 사용할 수 있는 일반화 클래스

IEnumerable과 IEnumerator

 

[C#] IEnumerable, IEnumerator 그리고 yield

enumerate 영어로 수를 세다. 카운팅 하다! 두 인터페이스는 열거자와 관련이 있다.(반복자와 동일한…것 같다. 아닐수도..) using System.Collections; C#의 모든 Collections 컬렉션은 IEnumerable, IEnumerator를 상

ansohxxn.github.io

foreach를 사용할 수 있는 클래스를 만들기 위해서는,
IEnumerable 인터페이스와 IEnumerator 인터페이스를 상속하고 메소드와 프로퍼티를 구현해야 함

 

메소드 설명
IEnumerator GetEnumerator IEnumerator 형식의 객체를 반환(IEnumerable로부터 상속받은 메소드)
IEnumerator<T> GetEnmerator IEnumerator<T> 형식의 객체를 반환
메소드 설명
boolean MoveNext() 다음 요소로 이동합니다. 컬렉션의 끝을 지난 경우에는 false, 
이동이 성공한 경우에는 true를 반환합니다.
void Reset() 컬렉션의 첫 번째 위치의 "앞"으로 이동합니다.
첫 번째 위치가 0번일 때, Reset()을호출하면 -1번으로 이동합니다.
첫 번째 위치로의 이동은 MoveNext()를 호출한 다음에 이루어집니다.
Object Current { get; } 컬렉션의 현재 요소를 반환(IEnumerator로부터 상속받은 프로퍼티).
T Current { get; } 컬렉션의 현재 요소를 반환합니다.
// IEnumerable: 열거자를 리턴하는 Getter의 Getter
// IEnumerator: 데이터를 리턴(Getter)하는 열거자
class MyList<T> : IEnumerable<T>, IEnumerator<T>
{
    #region [MyList getter, setter, Length]
    private T[] array;
    int position = -1;

    // 생성자: array를 길이가 3인 배열로 초기화
    public MyList() { array = new T[3]; }
    public T this[int index]
    {
        get { return array[index]; }
        set { // array 길이보다 index가 클 경우 array를 리사이즈
            if (index >= array.Length) {
                Array.Resize<T>(ref array, index + 1);
                Console.WriteLine($"Array Resized : {array.Length}");
            }
            array[index] = value;
        }
    }

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

    #region [IEnumerator 구현]
    // IEnumerator를 리턴하는 모든 함수는 ref, out 매개변수가 허용되지 않으며, 또한 람다 함수에서 사용할 수도 없음
    // IEnumerable: 열거자 IEnumerator를 Get하는 데 필요한 인터페이스
    // IEnumerator: 열거자를 구현하는 데 필요한 인터페이스
    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < array.Length; i++) {
            yield return (array[i]);
        }
    }

    // GetEnumerator: 컬렉션을 반복하는 데 사용할 수 있는 IEnumerator를 리턴
    IEnumerator IEnumerable.GetEnumerator()
    {
        for (int i = 0; i < array.Length; i++) {
            yield return (array[i]);
            // array[i]가 IEnumerator로 형변환되어 리턴됨
        }
    }

    // Current: 현재 위치의 데이터를 object 타입으로 리턴
    public T Current
    {
        get { return array[position]; }
        // T 객체인 array[position]을 리턴함
        // 이건 IEnumerable 인터페이스를 구현한 프로퍼티가 아님
    }

    object IEnumerator.Current
    {
        get { return array[position]; }
        // array[position]을 System.Object로 업캐스팅 형변환하여 리턴함
        // 이건 IEnumerable 인터페이스를 구현한 프로퍼티가 맞음
    }

    // MoveNext: 다음 위치로 이동하는데, 다음 위치에 데이터 있으면 true, 없으면 false 리턴
    public bool MoveNext()
    {
        if (position == array.Length - 1)
        {
            Reset();
            return false;
        }

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

    // Reset: 인덱스를 초기 상태 위치로 이동시킴
    public void Reset()
    {
        position = -1;
    }

    public void Dispose() { }
    #endregion
}

class Program
{
   static void Main(string[] args)
    {
        MyList<string> str_list = new MyList<string>();
        str_list[0] = "adb";
        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] = 0;
        int_list[1] = 1;
        int_list[2] = 2;
        int_list[3] = 3;
        int_list[4] = 4;

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

활용예제

    class Program
    {
        static void Main(string[] args)
        {
            #region [제네릭]
            Print(10);
            Print("10");
            Print(true);
            #endregion

            #region [제네릭 X]
            Print<int>(10);
            Print<string>("10");
            Print<bool>(true);
            #endregion
        }

        #region [제네릭]
        public static void Print<T>(T data)
        {
            Console.WriteLine(data);
        }
        #endregion

        #region [제네릭 X]
        public static void Print(int data)
        {
            Console.WriteLine(data);
        }

        public static void Print(string data)
        {
            Console.WriteLine(data);
        }

        public static void Print(bool data)
        {
            Console.WriteLine(data);
        }
        #endregion

        #region [제네릭]
        // CodeBase를 상속받은 애만 처리!
        public static void Save<T>(T data) where T : CodeBase
        {
            // todo...
            Console.WriteLine(data.Code);
            Console.WriteLine(data.Name);
        }
        public static void SaveChit<T>(T data) where T : ChitBase
        {
            // todo...
            Console.WriteLine(data.Prod);
            Console.WriteLine(data.ProdCode);
        }
        #endregion

        #region [제네릭 X]
        public static void Save(Product data)
        {
            // todo...
            Console.WriteLine(data.Code);
            Console.WriteLine(data.Name);
        }

        public static void Save(Cust data)
        {
            // todo...
            Console.WriteLine(data.Code);
            Console.WriteLine(data.Name);
        }
        #endregion
    }

    #region [Product, Cust]
    public interface CodeBase
    {
        string Code { get; set; }
        string Name { get; set; }
    }

    public class Product : CodeBase
    {
        public string Code { get; set; }
        public string Name { get; set; }
    }

    public class Cust : CodeBase
    {
        public string Code { get; set; }
        public string Name { get; set; }
    }
    #endregion

    #region [Purchase, Sale]
    public interface ChitBase
    {
        Product Prod { get; set; }
        string ProdCode { get; set; }
    }

    public class Purchase : ChitBase
    {
        public Product Prod { get; set; }
        public string ProdCode { get; set; }
    }

    public class Sale : ChitBase
    {
        public Product Prod { get; set; }
        public string ProdCode { get; set; }
    }
    #endregion

 

https://github.com/bonjenny/CHash/tree/main/11%EC%9E%A5%20-%20%EC%9D%BC%EB%B0%98%ED%99%94%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

반응형

'C# > 이것이 C#이다' 카테고리의 다른 글

14장. 람다식  (0) 2023.05.18
13장. 대리자와 이벤트  (0) 2023.05.18
10장. foreach가 가능한 객체 만들기  (0) 2023.05.18
9장. 프로퍼티  (0) 2023.05.16
8장. 인터페이스와 추상 클래스  (0) 2023.04.28