LAB/C#

C# ) C# 기초 4 (일반화 프로그래밍, 예외처리)

it-lab-0130 2024. 11. 1. 17:10
일반화 프로그래밍

특수한 개념으로부터 공통된 개념을 찾아 묶는 것을 ‘일반화.Generalization’라고 합니다.

일반화 프로그래밍GenericProgramming은 이러한 일반화를 이용하는 프로그래밍 기법 입니다. 일반화 프로그래밍은 일반화하는 대상이 조금 특이한데, 바로 '데이터 형식Data Type'입니다.

** 오버로딩을 하지 않고도 모든 형식을 지원하는 프로그래밍을 뜻한다.

 

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

 

사용예시

// 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[] source, T[] target)
{
	for(int i = 0; i< source.Length; i++)
    {
    	target[i] = source[i];
    }
}

데이터 형식이 사용된 부분을 T 기호로 치환하여 사용 / T는 '형식 Type'을 뜻합니다.

" T가 C#에서 지원하는 형식이 아니여서 컴파일 에러가 발생하지만 T가 구체적으로 어떤 형식인지 지정을 해줘야 합니다."

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

** 형식 매개변수로 입력하는 방법 : 메서드 이름 뒤에 <와>를 넣어주고 그사이에 T를 넣으면 T는 '형식 매개변수 Type Parameter'가 됩니다. 

CopyArray()를 호출할때 <> 사이에 T대신 형식으이 이름을 입력하면 컴파일러는 메서드의 나머지 부분에 대해서도 T를 형식 매개변수 값으로 치환합니다.

 

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

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

 

 

일반화 클래스

일반화 클래스는 (데이터 형식을) 일반화한 클래스입니다. 일반화 클래스를 선 언하는문법은다음과같습니다. 일반화메소드가그랬던 것처럼, 일반화클래스도형식 매개변수가 있는 것을 제외하면 보통의 클래스와 똑같습니다.

class 클래스_이름 < 형식_매개변수>
{
	// ...
}

 

사용 예시

// int 형식
class Array_Int
{
	private int[] array;
    //...
    public int GetElement(int index) {return array [index];}
}

// double 형식
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>();

 

using System;

namespace Generic
{
    class MyList<T>
    {
        private T[] array;

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

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

 

 

형식 매개변수 제약

일반화 메소드나 일반화 클래스가 입력받는 형식 매개변수 T는 ‘모든’ 데이터 형식을 대신할 수 있었 습니다. 이렇게 모든형식에 대응할수 있는형식 매개변수가필요한때도 있지만, 종종특정 조건을 갖춘 형식에만 대웅하는 형식 매개변수가 필요할 때도 있습니다. 이때 우리는 형식 매개변수의 조건 에제약을줄수있습니다.

 

class MyList<T> where T : MyClass
{
	//..
}

 

CopyArray()의 형식 매개변수 T에 ‘값 형식이어야 할 것’ 이라는 재약은 디음과 같이 줄 수 있습니다.

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

// 형식 매개변수 
where 형식_매개변수 : 제약조건

 

일반화 컬렉션

. 일반화 컬렉 션은 말 그대로 일반화에 기반해서 만들어져 있기 때문에 컴파일할 때 컬렉션에서 사용할 형식이 결 정되고, 쓸데없는 형식 변환을 일으키지 않습니다. 또한 잘못된 형식의 객체를 담게 될 위험도 피할 수있습니다. System.Collections.Generic 네임스페이스는 다양한 컬렉션 클래스를 담고 있지만, 지면의 한계 상 대표적인 다음 네 가지 클래스만 다루려 합니다.

• List<T>

• Queue<T>

• Stack<T>

• Dictionary<TKey, TValue>

 

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

일반화 클래스도 IEnumerable 인터페이스를 상속하면 일단은 foreach를 통해 순회할 수 있지만, 요소를 순회할 때마다 형식 변환을 수행하는 오버로드가 발생한다는 문제가 있습니다.

 

System.Collections.Generic 네임스페이스에 이 문제를 풀 수 있는 열쇠가 있습니다. IEnumerable  의 일반화 버전인 IEnumerable  인터페이스가 바로 그것입니다. 이 인터페이스 를 상속하면 형식 변환으로 인한 성능 저하가 없으면서도 foreach 순회가 가능한 클래스를 작성할 수있습니다. 

 

[ IEnumerable 인터페이스의 메소드 ]

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

 

[ IEnumerator <T>의 메서드와  프로퍼티 ]

 

메서드 또는 프로퍼티 설명
boolean MoveNext() 다음 요소로 이동합니다. 컬렉션의 끝을 지난 경우에는 false, 이동이 성공한 경우에는 true를 반환합니다.
void Reset() 컬렉션의 첫 번째 위치의 '앞'으로 이동합니다. 첫 번째 위치가 0번일 때, Reset()을 호출하면 -1번으로 이동하는 것이죠 . 첫 번째 위치로의 이동은 MoveNext()를 호출한 다음에 이루어집니다.
Object Current{get;} 컬렉션의 현재 요소를 반환합니다. (IEnumerator로부터 상속받은 프로퍼티).
T Current{get;} 컬렉션의 현재요소를 반환합니다.

 

예외처리
try
{
	// 실행하려는 코드
}
catch(예외_객체_1)
{
	// 예외가 발생했을 때의 처리
}
catch(예외_객체_2)
{
	// 예외가 발생했을 때의 처리
}

 

* System.Exception 클래스 : 모든 예외의 조상 , C#에서 모든 예외 클래스는 반드시 이 클래스로부터 상속받아야 합니다.

 

예외 던지기

* throw : try ~ catch문으로 예외를 받는다는것은 어디선가 예외를 던진다는 이야기입니다. 예외 던지는 키워드로 Throw가 사용됩니다.

 

try
{
	//...
    throw new Exception("예외를 던집니다."); // throw 문을 통해 던져진 예외 객체는 Catch문을 통해 받습니다.
}
catch(Exception e)
{
	Console.WriteLine(e.Message);
}

 

사용예시

static void DoSomething(int arg)
{
	if(arg < 10)
    {
    	Console.WriteLine("arg : {0}", arg);
	}
    else 
    {
    	throw new Exception("arg가 10보다 큽니다.");
        // 예외를 던졌지만 DoSomething() 메서드안에서는 이 예외를 처리할 수 있는 코드가 없습니다.
        // 이 예외는 DoSomething() 메서드의 호출자에게 던져집니다.
    }
}

static void Main()
{
	try
    {
    	DoSomething(13); // DoSomething() 메서드에서 던진 호출자의 try ~ catch블록에서 받습니다.
    }
    catch (Exception e)
    {
    	Console.WriteLine(e.Message);
    }
}

 

try ~ catch 와 finally

C#에서는 예외 처리를 할 때 자원 해제 같은 뒷 마무리를 우아하게 실행할 수 있도록 Finally 절을 try ~ catch 문과 함께 사용합니다.

try ~ catch 문의 마지막에 연결해서 사용한다.

try
{
	dbconn.Open(); //dbconn은 데이터베이스 커넥션
    //...
    dbconn.Close();
}
catch(XXXException e)
{
	dbconn.Close();
}
catch(YYYException e)
{
	dbconn.Close();
}
// 자원 해제 코드를 각 catch절마다 반복?

// finally 절 사용

try
{
	dbconn.Open(); // dbconn은 데이터베이스 커넥션
    //...
}
catch(XXXException e)
{
}
catch(YYYException e)
{
}
finally
{
	dbconn.Close();
}

try 절 안에서 return 문이나 throw 문이 사용되더라도(이 두 문장은 프로그램의 흐름 제어를 외부 코드로 옮깁 니다) finally 절은 꼭 실행됩니다.