일반화
일반화의 특징과 장점
클래스, 함수 일반화 가능
<T> 키워드 사용
박싱과 언박싱을 줄일 수 있음
불필요한 오버로딩을 줄일 수 있다
<T>키워드는 굳이 T일 필요는 없다. (U로 하든 AAA로 하든 자유다.)
아래 예시를 보자
static void GenericPrint<T>(T data) {
Console.WriteLine("data: {0}", data);
}
static void GenericPrint<T>(T[] arrData) {
for(int i = 0; i < arrData.Length; i++) {
Console.WriteLine("arrData: {0}", arrData[i]);
}
}
함수는 GenericPrint고 파라미터 하나를 받는다.
그리고 Console을 통해, 입력받은 파라미터를 출력한다.
두번째 함수는 같은 이름이지만 배열을 받는다.
static void Main(string[] args) {
int a = 10;
float b = 10.3f;
string c = "Hello";
int[] arrA = { 0, 1, 2, 3 };
string[] arrB = { "A", "B", "C", "D" };
GenericPrint(a);
GenericPrint(b);
GenericPrint(c);
Console.WriteLine();
GenericPrint(arrA);
Console.WriteLine();
GenericPrint(arrB);
}
메인함수의 모습, 첫번째 함수에 정수를 넣든, 소수를 넣든 문자열을 넣든 잘 작동한다.
이를 통해 알 수 있는 것은
일반화를 하면 데이터에 종속되지 않고 나름 확장성을 지니게 되는 것이다.
일반화 - 클래스
클래스에서는 일반화 키워드를 클래스 이름과 함께 적는다.
class GenericAA<T>
{
private T num;
public T GetNum() {
return num;
}
public void SetNum(T data) {
num = data;
}
}
이걸 메인함수로 가져와서 사용하면 아래와 같다.
class Program
{
static void Main(string[] args) {
GenericAA<int> AA = new GenericAA<int>();
AA.SetNum(100);
Console.WriteLine("AA: " + AA.GetNum());
GenericAA<float> BB = new GenericAA<float>();
BB.SetNum(100.30f);
Console.WriteLine("BB: " + BB.GetNum());
}
}
메인함수에서는 new를 통해 새로 생성할 때, 키워드에 자료형을 넣어준다.
이로써 클래스를 실행할 때, 넣어줄 값을 정해줄 수 있다.
일반화를 통해서, 클래스는 다양한 종류의 값을 받을 수 있게 된다.
dynamic
dynamic은 var타입이 가지고 있는 문제를 해결하는 용도로 사용할 수 있다.
또한 var와 object와 비슷한 느낌이 있다.
static T AddArray<T>(T[] arrDatas) {
//T temp = 0; 에러 발생
//object aaa = 0; //박싱, 언박싱 발생
dynamic temp = default(T);
for(int i = 0; i < arrDatas.Length; i++) {
temp += arrDatas[i];
}
return temp;
}
위 코드에서, 첫 번째 주석에서, T는 0으로 지정할 수 없다.
왜냐하면 T를 입력받을 때, 그 자료형이 무엇인지 알 수 없기 때문에 정수형으로 넣어줄 수 없는 것이다.
하지만 dynamic의 경우, var나 object처럼 다양하게 값을 받을 수 있으므로 에러가 발생하지 않는다.
dynamic과 var와 object는 어떻게 다를까?
var와 dynamic은 선언된 원본 형식을 유지한다.
그러나 이 둘은 타입이 확정되는 시점이 다르다.
var는 컴파일 시점에 이미 타입을 추정하여 확정하지만
dynamic은 런타임 시점에 타입이 확정된다.
때문에 dynamic은 올바르지 않은 타입이 지정되더라도 컴파일 시점에서는 모두 pass되며 런타임 시점에 에러가 발생한다.
일반화 컬렉션
C# 코드 최상단에 다음을 추가하여 사용할 수 있다.
using System.Collections.Generic;
이것은 컬렉션의 박싱과 언박싱의 단점을 해결하며 4가지 방식이 있다.
List<T>
Queue<T>
Stack<T>
Dictionary<T>
이것은 자료를 보관하는 통과 같으나
어떻게 보관하는지, 또 꺼낼때는 어떻게 꺼내는지에 따라 차이가 있다.
List부터 시작해보자
List
list는 말 그대로 데이터 리스트다.
후술할 다른 것들과는 다르게 딱히 데이터를 꺼낸다던가 하는 개념은 없고 인덱스를 통해 접근할 수 있다.
내부요소를 지울때는 clear()를 사용한다.
static void Main(string[] args) {
List<int> ListAA = new List<int>();
ListAA.Add(1);
ListAA.Add(2);
for(int i = 0; i < 10; i++) {
ListAA.Add(i);
}
foreach(var data in ListAA) {
Console.WriteLine("data: " + data);
}
Console.WriteLine("\n배열데이터로 초기화");
string[] arrData = { "AA", "BB", "CC" };
List<string> listArr = new List<string>(arrData);
for(int i = 0; i < arrData.Length; i++) {
Console.WriteLine("arrData: " + arrData[i]);
}
}
ListAA는 정수형만 받을 수 있게 되어있다.
그렇게 정수를 받고 foreach로 값을 출력한다.
이후 문자열만 받는 새로운 List를 만들고 거기에arrData를 넣은 다음 그걸 다시 출력한다.
출력결과
Queue
static void Main(string[] args) {
Queue<int> queueAA = new Queue<int>();
queueAA.Enqueue(1);
queueAA.Enqueue(2);
for(int i = 0; i < 10; i++) {
queueAA.Enqueue(i);
}
Console.WriteLine("queue data: {0}", queueAA.Peek());
while(queueAA.Count > 0) {
Console.WriteLine("queue data: {0}, count: {1}", queueAA.Dequeue(), queueAA.Count);
}
Console.WriteLine("\n배열데이터로 초기화");
string[] arrData = { "AA", "BB", "CC"};
Queue<string> queueArr = new Queue<string>(arrData);
foreach(var data in queueArr) {
Console.WriteLine("queueArr data: " + data);
}
}
큐는 파이프와 같다.
값을 꺼낼 때, 가장 오래된 데이터가 나온다.
지금 for을 통해서, 1에서 9까지 차례대로 데이터를 넣고 있는데
Dequeue를 통해서 꺼낸 값은 가장 먼저 들어간 값부터 차례대로 나오고 있다.
출력결과
Stack
static void Main(string[] args) {
Stack<string> stack = new Stack<string>();
stack.Push("a");
stack.Push("b");
stack.Push("c");
Console.WriteLine("stack data: {0}", stack.Peek());
while(stack.Count > 0) {
Console.WriteLine("stack data: {0}, count: {1}", stack.Pop(), stack.Count);
}
//배열데이터로 초기화
Console.WriteLine("\n배열데이터로 초기화");
int[] arrData = { 100, 200, 300 };
Stack<int> stackCopy = new Stack<int>(arrData);
foreach(object data in stackCopy) {
Console.WriteLine("stackCopy data: " + data);
}
}
stack에선 Push로 값을 넣고 Peek로 그 값을 살펴보고
Count로 값을 세고 Pop으로 값을 꺼낸다.
stack은 통 안에 쌓는 느낌이다.
이는 주로 실행취소(undo) 같은 기능에 사용하는데
Pop을 통해 데이터를 꺼내면 가장 최근에 입력된 데이터가 출력된다.
Peek는 지금 당장 꺼낼 수 있는 값이 무엇인지, 즉, stack의 최상단에 위치한 값을 출력(확인)한다.
출력결과
Dictionary
딕셔너리는 자바스크립트나 웹개발에서 자주 사용되는 그것과 비슷하다.
여러가지 데이터를 제목과 내용 같은 느낌으로 저장하는데 사실 제한이 없다.
여기서 제목에 속하는것이 key, 내용에 해당하는 것이 value다.
예를들어, AA 딕셔너리의 1번키는 a_a 1번벨류는 a_b, 2번키는 b_a, 2번 벨류는 b_b... 이런 형태로 저장되는 것이다.
즉 AA의 1번 키의 벨류값을 가져오라고 한다면 a_b가 나올 것이다.
static void Main(string[] args) {
Dictionary<string, int> dictionaryAA = new Dictionary<string, int>();
dictionaryAA.Add("10", 10);
dictionaryAA.Add("20", 20);
dictionaryAA["30"] = 30;
foreach(var key in dictionaryAA.Keys) {
Console.WriteLine("key: {0}, data: {1}", key, dictionaryAA[key]);
}
Console.WriteLine("");
Dictionary<int, string> dictionaryInit = new Dictionary<int, string>() {
[1] = "Hello",
[100] = "Dictionary",
[1000] = "Good!!",
};
foreach(var key in dictionaryInit.Keys) {
Console.WriteLine("key: {0}, data: {1}", key, dictionaryInit[key]);
}
string getValue = string.Empty;
dictionaryInit.TryGetValue(1, out getValue);
Console.WriteLine("\nTryGetValue: " + getValue);
dictionaryInit.TryGetValue(11, out getValue);
Console.WriteLine("\nTryGetValue: " + getValue);
딕셔너리를 처음 선언하는 곳을 보면 string과 int로 선언되었는데, 사실 2개 말고도 더 많이 넣을 수도 있다.
foreach를 보면 dictionaryAA.keys 라고 되어 있는데, 이는 dictionaryAA가 가진 key값들을 말한다.
출력결과
key가 key값이고 data가 그 key가 가진 value값이다