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

ssu_hyun·2022년 4월 28일
1

C#

목록 보기
15/22

Key point

  • 일반화 프로그래밍
  • 일반화 메소드
  • 일반화 클래스
  • .NET 제공 일반화 컬렉션

11.1 일반화 프로그래밍이란?

일반화(Generalization)

특수한 개념으로부터 공통된 개념을 찾아 묶는 것
ex) 포유류 - 사람, 돼지, 오리너구리, 고래

일반화 프로그래밍

  • 데이터 형식(Data Type) 일반화를 이용하는 프로그래밍 패러다임
  • 특수한 형식을 사용하는 코드의 데이터 형식을 일반화해 오버로딩하지 않고도 모든 형식을 지원할 수 있도록 함

11.2 일반화 메소드(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];
     }
     
     // 일반화
     void CopyArray<T>(T[] source, T[] target)
    {
         for (int i = 0; i < source.Length; i++)
             target[i] = source[i];
     }

예제

using System;

namespace CopyingArray
{
    class MainApp
    {
    	// 일반화 메소드 선언
        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];
			
            // 형식매개변수 T에 int 대입해 호출 (컴파일 단계에서 int로 치환)
            CopyArray<int>(source, target);

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

            string[] source2 = { "하나", "둘", "셋", "넷", "다섯" };
            string[] target2 = new string[source2.Length];
			
            // 형식매개변수 T에 string 대입해 호출 (컴파일 단계에서 string로 치환)
            CopyArray<string>(source2, target2);

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


11.3 일반화 클래스

  • 데이터 형식을 일반화한 클래스
  • 클래스 이름 뒤와 실제 형식 자리에 형식 매개 변수 입력
class 클래스이름<형식매개변수>
{
	//...
}

// 기능이 같은 클래스1
class Array_Int
{
    private int[] array;
    //...
    public int GetElement( int index ) { return array[index];}
}

// 기능이 같은 클래스2
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>();

// 매개변수 T는 객체 생성 시 입력받은 형식으로 치환되어 다음과 같이 컴파일됨
// int형식으로 사용될 때
class Array_Generic
{
    private int[] array;
    //...
    public int GetElement( int index ) { return array[index];}
}

// double 형식으로 사용될 때
class Array_Generic
{
    private double[] array;
    //...
    public double GetElement( int index ) { return array[index];}
}

예제

using System;

namespace Generic
{
    class MyList<T>  // 형식 매개변수 T 추가
    {
        private T[] array;

        public MyList()
        {
            array = new T[3]; // 배열 길이 = 3
        }
		
        // 인덱서
        public T this[int index]  
        {
            get
            {
                return array[index];
            }

            set
            {
            	// 배열 길이 작으면 재조정, 늘리기 + 값 부여
                if (index >= array.Length)  
                {
                    Array.Resize<T>(ref array, index + 1);  
                    Console.WriteLine($"Array Resized : {array.Length}");
                }

                array[index] = value;  
            }
        }
		
        // array길이
        public int Length
        {
            get { return array.Length; }
        }
    }

    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";

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


11.4 형식 매개변수 제약시키기

  • 특정 조건을 갖춘 형식에만 대응되도록 할 때 제약
  • 형식 제약 문법
		where 형식매개변수 : 제약조건

예제

using System;

namespace ConstraintsOnTypeParameters
{
    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 CopyyArray<T>(T[] Target) where T : U  // T는 또 다른 형식 매개변수 U로부터 상속받은 클래스여야 함
        {
            Target.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;

            Console.WriteLine("a");
            for (int i = 0; i < a.Array.Length; i++)
                Console.WriteLine(a.Array[i]);

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

            Console.WriteLine("b");
            for (int i = 0; i < b.Array.Length; i++)
                for (int j = 0; j < b.Array[i].Array.Length; j++)
                    Console.WriteLine(b.Array[i]);

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

            Console.WriteLine("c");
            for (int i = 0; i < c.Array.Length; i++)
                Console.WriteLine(c.Array[i]);

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

            Console.WriteLine("d");
            for (int i = 0; i < d.Array.Length; i++)
                Console.WriteLine(d.Array[i]);

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

            Console.WriteLine("e");
            for (int i = 0; i < e.Array.Length; i++)
                Console.WriteLine(e);
        }
    }
}



11.5 일반화 컬렉션

  • 컬렉션은 object 형식 기반으로 요소에 접근할 때마다 박싱/언박싱이 일어나 성능이 저하되는 문제 발생
  • 일반화 컬렉션은 형식매개변수가 컴파일 단계에서 특정 형식으로 치환되어 버그와 성능저하를 줄임
  • 일반화 컬렉션은 System.Collections.Generic 네임스페이스에 위치
  • List<T>, Queue<T>, Stack<T>, Dictionary<TKey, TValue>
    • 형식 매개변수를 요구하는 것 외에는 컬렉션(ArrayList, Queue, Stack, Hashtable)의 기능과 같다.

11.5.1 List<T>

using System;
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 element in list)
                Console.Write($"{element} ");
            Console.WriteLine();

            list.RemoveAt(2);

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

            list.Insert(2, 2);

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


11.5.2 Queue<T>

using System;
using System.Collections.Generic;

namespace UsingGenericQueue
{
    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());
        }
    }
}


11.5.3 Stack<T>

using System;
using System.Collections.Generic;

namespace UsingGenericStack
{
    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());
        }
    }
}


11.5.4 Dictionary<TKey, TValue>

using System;
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";
            dic["넷"]   = "four";
            dic["다섯"] = "five";

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


11.6 IEnumerable<T> 인터페이스

  • foreach 사용가능한 일반화 클래스
  • 형식변환으로 인한 성능 저하가 없으면서도 foreach 수회가 가능한 클래스를 작성할 수 있음
  • IEnumerable<T> 인터페이스 메소드
  • IEnumerator<T> 메소드&프로퍼티
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($"Array Resized : {array.Length}");
                }

                array[index] = value;
            }
        }

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

        public IEnumerator<T> GetEnumerator()
        {
            return this;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this;
        }

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


연습문제

  1. 다음 코드에서 문제를 찾고, 그 원인을 설명하세요.
Queue queue = new Queue();
queue.Enqueue(10);
queue.Enqueue("한글");
queue.Enqueue(3.14);

Queue<int> queue2 = new Queue();
queue.Enqueue(10);
queue.Enqueue("한글");
queue.Enqueue(3.14);

=> Queue는 object형식으로 어떤 데이터 형식이 들어가도 상관없지만 박싱과 언박싱이 주기적으로 일어나 성능 문제를 가지고 있다.

  1. 다음 코드에서 ⓐ에 들어갈 내용은 무엇입니까?
Dictionary</*  ⓐ  */> dic = new Dictionary</*  ⓐ  */>();

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

Console.WriteLine(dic["하나"]);
Console.WriteLine(dic["둘"]);
Console.WriteLine(dic["셋"]);
Console.WriteLine(dic["넷"]);
Console.WriteLine(dic["다섯"]);

=> string, string

1개의 댓글

comment-user-thumbnail
2023년 2월 16일

1번에서 괄호 안에 있는 int 이부분도 잘못된 거 아닌가요?

답글 달기