2020. 1. 23. 19:57ㆍC# 언어/이것이 C# 이다. 책정리
챕터 11 '일반화 프록래밍'입니다
우리가 작성한 하나의 코드가 여러 가지 데이터 형식에 맞춰 동작할 수 있도록 하는 기법입니다.
'이것이 C#이다' 교재를 바탕으로 정리했습니다.
이전 정리 글
2020/01/06 - [이것이 C#이다./이것이 C# 이다. 책 정리] - Ch01 프로그래밍을 시작합시다.
2020/01/06 - [이것이 C# 이다./이것이 C# 이다. 책정리] - Ch02 처음 만드는 C# 프로그램
2020/01/06 - [이것이 C# 이다./이것이 C# 이다. 책정리] - Ch03 데이터 보관하기
2020/01/07 - [이것이 C#이다./이것이 C# 이다. 책 정리] - 부록A. 문자열 다루기
2020/01/07 - [이것이 C# 이다./이것이 C# 이다. 책정리] - Ch04. 데이터를 가공하는 연산자
2020/01/08 - [이것이 C# 이다./이것이 C# 이다. 책정리] - Ch05 코드의 흐름 제어하기
2020/01/09 - [이것이 C#이다./이것이 C# 이다. 책 정리] - ch06 메소드로 코드 간추리기
2020/01/11 - [이것이 C# 이다./이것이 C# 이다. 책정리] - ch07 클래스
2020/01/17 - [이것이 C# 이다./이것이 C# 이다. 책정리] - ch08 인터페이스와 추상 클래스
ㅇ 일반화 프로그래밍이란? |
특수한 개념으로부터 공통된 개념을 찾아 묶는 것을 "일반화"라고 합니다.
프로그래밍의 일반화하는 대상은 "데이터 형식"입니다.
예를 들어서 정수형 1차원 배열을 복사하는 메서드가 있습니다.
void CopyArray ( int [] source, int [] target )
{
for(int i = 0; i < source.Length; i++)
target [i] = source [i];
}
CopyArray() 메서드를 사용하던 중 이번에는 문자열 배열을 복사하는 기능이 필요해졌습니다.
그래서 CopyArray()를 오버 로딩했습니다.
void CopyArray ( string [] source, string [] target )
{
for(int i = 0; i < source.Length; i++)
target [i] = source [i];
}
이번에는 MyClass 형식의 배열을 복사해야 할 필요가 생겼습니다. 이처럼 모든 형식을
오버 로딩으로 작성해야 될까요?
특수한 형식을 사용하는 코드를 일반화한다면 CopyArray() 메서드를 오버 로딩하지 않고도
모든 형식을 지원할 수 있지 않을까요?
일반화 프로그래밍은 바로 이런 아이디어를 바탕으로 만들어진 프로그래밍 패러다임입니다.
ㅇ 일반화 메소드 |
일반화 메서드는 이름처럼 (데이터 형식) 일반화한 메서드입니다.
일반화할 형식이 들어갈 자리에 구체적인 형식의 이름 대신 "형식 매개 변수"가 들어갑니다.
앞에서 봤던 CopyArray()의 int형식과 string형식을 일반화해보겠습니다.
void CopyArray <T> ( T [] source, T [] target )
{
for(int i = 0; i < source.Length; i++)
target [i] = source [i];
}
T는 형식을 뜻합니다. C#이 지원하는 형식이 아님을 알고 있으므로
컴파일하려면 T가 구체적으로 어떤 형식인지 알려줘야 합니다.
일반화한 메서드를 호출하는 방법입니다.
int [] source = {1, 2, 3, 4, 5};
int[] target = new int [source.Length];
CopyArray <int>(source, target); // 형식 매개 변수 T에 int를 대입합니다.
foreach( int element in target )
WriteLine(element);
ㅇ 일반화 클래스 |
일반화 클래스는 (데이터 형식을) 일반화한 클래스입니다.
일반화 메서드와 동일한 흐름입니다.
두 개의 클래스가 있습니다.
class Array_Int
{
private int [] array;
// '''
public int GetElement( int index ) { reutrn array [index]; }
}
class Array_Double
{
private double [] array;
// '''
public double GetElement( int index ) { reutrn array [index]; }
}
데이터 형식을 제외하면 다른 부분들은 서로 동일하기 때문에 일반화가 가능합니다.
다음은 위 클래스를 일반화한 클래스입니다.
class Array_Generic <T>
{
private T [] array;
// '''
public T GetElement( int index ) { reutrn array [index]; }
}
Array_Generic를 사용하는 방법이다.
Array_Generic <int> intArr = new Array_Generic <int>();
Array_Generic <double> dbArr = new Array_Generic <double>();
ㅇ 형식 매개 변수 제약시키기 |
종종 특정 조건을 갖춘 형식에만 대응하는 형식 매개 변수가 필요할 때도 있습니다.
형식 매개 변수의 조건에 제약을 줄 수 있습니다.
다음 표에는 제약을 주는 방법입니다.
제약 | 설명 |
where T : struct | T는 값 형식이어야 합니다. |
where T : class | T는 참조 형식이어야 합니다. |
where T : new() | T는 반드시 매개 변수가 없는 생성자가 있어야 합니다. |
where T : 기반_클래스_이름 | T는 명시한 기반 클래스의 파생 클래스여야 합니다. |
where T : 인터페이스_이름 | T는 명시한 인터페이스를 반드시 구현해야 합니다. 다중 상속또한 가능합니다. |
where T : U | T는 또 다른 형식 매개 변수 U로부터 상속받은 클래스여야 합니다. |
크게 설명할 2가지 where T : new()와 U에 대해서 설명하겠습니다.
where T : new()
GreateInstance <T>() 메서드는 기본 생성자를 가진 어떤 클래스의 객체라도 생성해줍니다.
public static T CreateInstance <T>() where T : new()
{
return new T();
}
이 메서드를 호출할 때 기본 생성자가 없는 클래스를 형식 매개 변수로 넘기면 컴파일 에러가 납니다.
where T : U
상위 코드에서 사용되던 형식 매개 변수 U로부터 상속받는 형식으로 제약 조건을 주는 예입니다.
다음 코드의 CopyArray <T>()는 소속 클래스인
BaseArray <U>의 형식 매개 변수 U로부터 T가 상속받아야 할 것을 강제하고 있습니다.
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);
}
}
이 메서드를 호출할 때 기본 생성자가 없는 클래스를 형식 매개 변수로 넘기면 컴파일 에러가 납니다.
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
using System;
using static System.Console;
namespace p384
{
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
{
}
}
interface IDictionary1<T> { }
interface IDictionary2<T> { }
class SampleClass1<T> where T : IDictionary1<T> { }
class SampleClass2<T> where T : IDictionary2<T> { }
class MainApp
{
public static T CreateInstance<T>() where T : new()
{
return new T();
}
static void Main(string[] args)
{
// 값 형식 일반화 클래스
// - struct는 값 형식으로 제안한다.
// - StructArray<int>()의 인스턴스는 배열의 크기다.
StructArray<int> a = new StructArray<int>(3);
a.Array[0] = 0;
a.Array[1] = 1;
a.Array[2] = 2;
//a.Array[3] = 3; 크기가 3인 배열이므로 범위가 넘어가서 에러
//a.Array[2] = "에러"; int형식으로 선언했으므로 string형식 에러
WriteLine("--------------------------------");
WriteLine($"a.Array[0] : {a.Array[0]}");
WriteLine($"a.Array[1] : {a.Array[1]}");
WriteLine($"a.Array[2] : {a.Array[2]}");
WriteLine("--------------------------------");
WriteLine();
// 참조 형식 일반화 클래스
// - class는 참조 형식으로 제안한다.
// - RefArray<StructArray<double>>()의 인스턴스는 배열의 크기다.
RefArray<StructArray<double>> b = new RefArray<StructArray<double>>(4);
b.Array[0] = new StructArray<double>(5);
b.Array[1] = new StructArray<double>(10);
b.Array[2] = new StructArray<double>(1005);
WriteLine("--------------------------------");
WriteLine($"b.Array[0] : {b.Array[0]}");
WriteLine($"b.Array[1] : {b.Array[1]}");
WriteLine($"b.Array[2] : {b.Array[2]}");
WriteLine("--------------------------------");
WriteLine();
BaseArray<Base> c = new BaseArray<Base>(3);
c.Array[0] = new Base();
c.Array[1] = new Derived();
c.Array[2] = CreateInstance<Base>();
WriteLine("--------------------------------");
WriteLine($"c.Array[0] : {c.Array[0]}");
WriteLine($"c.Array[1] : {c.Array[1]}");
WriteLine($"c.Array[2] : {c.Array[2]}");
WriteLine("--------------------------------");
WriteLine();
BaseArray<Derived> d = new BaseArray<Derived>(3);
d.Array[0] = new Derived();
d.Array[1] = CreateInstance<Derived>();
d.Array[2] = CreateInstance<Derived>();
WriteLine("--------------------------------");
WriteLine($" d.Array[0] : { d.Array[0]}");
WriteLine($" d.Array[1] : { d.Array[1]}");
WriteLine($" d.Array[2] : { d.Array[2]}");
WriteLine("--------------------------------");
WriteLine();
BaseArray<Derived> e = new BaseArray<Derived>(3);
e.CopyArray<Derived>(d.Array);
WriteLine($" d.Array : { d.Array}");
}
}
}
|
ㅇ 일반화 컬렉션 |
컬렉션은 object 형식에 기반하고 있기 때문에 태생적으로 성능 문제를 안고 있습니다.
컬렉션의 요소에 접근할 때마다 형식 변환이 주야장천 일어나기 때문이죠.
일반화 컬렉션은 object 형식 기반의 컬렉션이 갖고 있던 문제를 말끔히 해결합니다.
일반화 컬렉션은 말 그대로 일반화에 기반해서 만들어져 있기 때문에
컴파일할 때 컬렉션에서 사용할 형식이 결정되고,
쓸데없는 형식 변환을 일으키지 않습니다.
대표적인 다음 네 가지 클래스만 다룹니다.
List <T> 클래스
ArrayList와 같은 기능을 하며, 사용법 역시 동일합니다.
차이점이라면 List<T> 클래스는 인스턴스를 만들 때 형식 매개 변수를 필요로 하고
아무 형식의 객체나 선언 가능했던 ArrayList와 달리
List <T>는 형식 매개 변수로 입력한 형식 외에는 입력을 허용하지 않습니다.
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
using System;
using static System.Console;
using System.Collections.Generic;
namespace p388
{
class Program
{
static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 0; i < 5; i++)
{
}
foreach (int elemnet in list)
{
Write($"{elemnet} ");
}
WriteLine();
list.Remove(2);
foreach (int elemnet in list)
{
Write($"{elemnet} ");
}
WriteLine();
list.Insert(2, 2);
foreach (int elemnet in list)
{
Write($"{elemnet} ");
}
WriteLine();
}
}
}
|
Queue <T> 클래스
Queue와 같은 기능과 사용법도 동일하다.
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
using System;
using System.Collections.Generic;
namespace p389
{
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> 클래스
역시 Stack과 동일합니다.
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
using System;
using System.Collections.Generic;
namespace p389
{
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()}");
}
}
}
|
Dictionary <TKey, Value> 클래스
Hashtable의 일반화 버전입니다.
그 사용법과 기능은 동일합니다.
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
using System;
using System.Collections.Generic;
using static System.Console;
namespace p389
{
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";
WriteLine(dic["하나"]);
WriteLine(dic["둘"]);
WriteLine(dic["셋"]);
WriteLine(dic["넷"]);
WriteLine(dic["다섯"]);
}
}
}
|