컬렉션이란? 아래의 기능을 사용하여 여러 개의 데이터를 하나의 구조로 그룹화하여 관리하는 기능입니다.
using System.Collections;
using System.Collections.Generic; //컬렉션에서 제네릭을 사용하기 위한 코드
데이터의 집합을 저장하고 조작하는데 필요한 다양한 기능을 제공하며, C#의 컬렉션 클래스와 인터페이스는 다음과 같은 주요 기능과 특징을 갖습니다.
- Array: 고정된 크기의 데이터 집합을 저장, 인덱스를 통해 요소에 접근할 수 있습니다.
//배열 Array 타입 예시
int[] arr = { 5, 1, 6, 3, 7 };
Array.Sort(arr); //Sort, 오름차순 정리
foreach (int i in arr)
{
Console.WriteLine(i); //오름차순 정리된 arr값들 출력
}
- ArrayList
//리스트 중 비제네릭 컬렉션 ArrayList 타입 예시
ArrayList arrList = new ArrayList();
//1. 자료형 상관없이 어떤 타입의 객체든 저장할 수 있습니다.
//2. 내부적으로 모든 항목은 object 타입으로 저장됩니다.
//3. 그 때문에 타입 안전성이 보장되지 않습니다.
//4. 쓸 때마다 자료형을 지정해야함.
arrList.Add(5);
arrList.Add(3.14);
arrList.Add("Hello");
//Boxing, Unboxing
object obj = "World";
string str = (string)obj;
ArrayList는 비제네릭 컬렉션으로 어떤 자료형, 데이터 타입 상관없이 object 타입으로 변환하여 저장합니다. 이로 인해 타입 안전성이 보장되지 않으며, 요소를 사용할 때마다 적절한 타입으로 캐스팅해야합니다. 그리고 이 과정에서 Boxing, Unboxing에 대해 알아보아야합니다.
- Boxing: int, float, bool, string 등 기본 데이터 타입을 object 타입으로 변환하는 과정입니다. 기본 타입 값을 object 타입의 참조 타입으로 감싸는 것입니다.
- 위 코드 중 arrList는 요소를 꺼낼 때마다 적절한 타입으로 캐스팅이 필요합니다.
- List<T>는 제네릭 컬렉션으로 특정 타입의 요소만 저장할 수 있습니다. 이로 인해 타입 안전성이 보장됩니다. 지정된 타입은 모두 T로 저장되며, 타입 캐스팅이 필요없습니다.
//리스트 중 제네릭 컬렉션 List<T> 타입 예시
List<double> list = new List<double>();
list.Add(1.1);
list.Add(2.2);
list.Add(3.3);
list[0] = 11.1;
list.Sort();
//배열은 Range, List는 Count
위의 코드에서는 double 타입의 요소만 저장할 수 있습니다. 여기서 list의 구성 요소는 {1.1, 2.2, 3.3}입니다. 그런데 list[0] = 11.1;이 첫번째 요소를 11.1로 초기화시키는 코드이기 때문에 {11.1, 2.2, 3.3}이 됩니다.
foreach (double d in list)
Console.WriteLine(d);
만약 위의 출력문을 밑에 추가로 작성한다면 2.2 \n 3.3 \n 11.1 이 출력됩니다. 그 이유는 list.Sort();가 있기 때문입니다. Sort()는 구성 요소들을 오름차순으로 재배열해달라는 코드입니다.
- Dictinary<TKey, TValue> : 키와 쌍을 저장하는 컬렉션입니다. 키를 통해 값에 빠르게 접근할 수 있습니다.
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("Hello", 100);
dic.Add("World", 200);
dic["Hello"] = 300;
위의 코드에서는 dic이라는 Dictinary 컬렉션에서 string과 int를 다루고 있으며, Hello 키에 100, World 키에 200을 추가했습니다. 그리고 Hello라는 키에 대한 값을 300으로 초기화시켜줍니다. 아래의 출력문에 주석으로 출력값을 표시했습니다.
Console.WriteLine(dic["Hello"]); //출력값: 300
//Console.WriteLine(dic[1]); //배열 형식의 출력문은 안됨.
Console.WriteLine(dic.ContainsKey("Hello")); //해당 키가 있으면 True 없으면 False 출력값: True
//dic.Keys
foreach (KeyValuePair<string, int> kvp in dic)
{
Console.WriteLine("Key: {0} / Value: {1}", kvp.Key, kvp.Value);
}
// foreach문 출력값
// Key: Hello / Value: 300
// Key: World / Value: 200
이외에 종류 3가지 컬렉션 타입은 간략하게 설명하겠습니다.
- HashSet<T>: 중복 없는 고유한 요소의 집합을 저장, 집합 연산(합, 교집합 등)을 지원합니다.
//HashSet<T> 예시
HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3, 4, 5 };
- Queue<T>: 선입선출 (First In First Out) 원칙에 따라 요소를 관리합니다. 데이터를 순서대로 처리하는 데 사용합니다.
//Queue<T> 예시
Queue<string> queue = new Queue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
- Stack<T>: 후입선출(Last In First Out) 원칙에 따라 요소를 관리합니다. 데이터를 최근 항목을 먼저 처리합니다.
//Stack<T> 예시
Stack<string> stack = new Stack<string>();
stack.Push("First");
stack.Push("Second");
이제 위를 응용해보겠습니다.
//DataHolder.cs
class DataHolder : IComparable<DataHolder> //DataHolder를 이용한
{
public string str = string.Empty;
public int i = 0;
public float f = 0.0f;
public DataHolder(string _str, int _i, float _f)
{
str = _str;
i = _i;
f = _f;
}
public int CompareTo(DataHolder _other) //IComparable 관련
{
return this.f.CompareTo(_other.f);
}
public override string ToString()
{
return string.Format("< {0} / {1} / {2} >", str, i, f);
}
}
위는 DataHolder이름의 스크립트로 str, i ,f를 사용하는 DataHolder(str, i, f)라는 객체를 만들어줍니다. 그리고 두 개의 메서드를 사용합니다.
- CompareTo: IComparable 인터페이스를 구현하여 DataHolder 객체를 f 필드(부동 소수점 숫자)를 기준으로 비교하여 정렬 기준을 제공합니다.
- ToString: DataHolder 객체의 상태를 문자열로 표현하여 쉽게 출력할 수 있게 해줍니다.
이제 이를 다루는 Program 스크립트를 살펴보겠습니다.
using System;
using System.Collections;
using System.Collections.Generic; //제네릭 컬렉션 사용
//유니티가 아닌 일반 C#을 다루면 기본 using System;이 깔린 상태
//Collections도 포함.
class Program
{
static int CompareDataHolder(DataHolder _lhs, DataHolder _rhs) //DataHolder.cs의 좌변, 우변 비교
{
//_lhs.i - _rhs.i DataHolder.cs의 int자료형의 i를 가지고있는 좌변, 우변의 값을 비교.
return _lhs.i.CompareTo(_rhs.i); //좌변에 i를 우변의 i와 비교하는 것.
}
public class DataHodlerComparer : IComparer<DataHolder>
{
int IComparer<DataHolder>.Compare(DataHolder _lhs, DataHolder _rhs)
{
return _lhs.str.Length - _rhs.str.Length;
}
}
}
- CompareDataHolder는 DataHolder.cs에서 int 자료형 i를 가지고 있는 좌변, 우변을 비교하는 메서드입니다. _lhs.i.CompareTo(_rhs.i)가 그 역할을 해줍니다.
- DataHolderComparer는 IComparer<DataHolder> 인터페이스를 구현하여 두 DataHolder _lhs, _rhs의 str 필드의 길이를 비교합니다.
이제 이 두 문을 이용하는 코드를 보겠습니다.
class Program
{
static void Main()
{
//DataHolder[] dataHodlers;
List<DataHolder> dataHolderList = new List<DataHolder>();
dataHolderList.Add(new DataHolder("A", 10, 10.1f));
dataHolderList.Add(new DataHolder("B", 2, 0.2f));
//1
//Comparison<DataHolder> comparison = new Comparison<DataHolder>(CompareDataHolder);
//dataHolderList.Sort(comparison); //위의 비교문의 오름차순의 Sort(); A의 값이 3이상이면 B가 먼저 호출
//dataHolderList.Sort(); //()괄호 안에 ctrl, shift, space 누르면 Sort관련이 나옴.
//익명, 무명 메소드
Comparison<DataHolder> comparison = new Comparison<DataHolder>(delegate (DataHolder _lhs, DataHolder _rhs)
{
return _lhs.i.CompareTo(_rhs.i);
}
);
dataHolderList.Sort(comparison);
//2
//dataHolderList.Sort(new DataHolderComparer());
//3
//dataHolderList[0].CompareTo();
//dataHolderList.Sort();
foreach (DataHolder data in dataHolderList)
{
//Console.WriteLine("{0} / {1} / {2}", data.str, data.i, data.f);
Console.WriteLine(data.ToString());
}
}
}
List<T>를 이용하여 DataHolder를 다루는 dataHolderList 제네릭 리스트를 만듭니다. 그리고 요소에 추가합니다. 이제 델리게이트(delegate)에 대해 알아봐야합니다.
- 대리자(delegate): 메서드를 참조할 수 있는 타입입니다. 대리자는 메서드의 참조를 담고 있는 변수라고 생각할 수 있습니다. 이를 통해 메서드를 변수처럼 다루거나 메서드를 인자로 전달할 수 있습니다.
- 대리자의 정의: 대리자는 메서드의 시그니처(반환형과 매개변수 목록)를 정의하는 타입입니다. 이 시그니처에 맞는 메서드들을 대리자 변수에 할당할 수 있습니다.
- 콜백 메서드: 콜백이란 특정 작업이 끝난 후 호출되는 메서드입니다. 대리자는 이 콜백 메서드를 저장하고 호출할 때 사용됩니다. 즉, 특정 이벤트나 작업이 완료된 후 호출되기를 기다리는 메서드입니다.
이제 구문을 해석하겠습니다. 여기에는 익명(무명) 메서드라는 개념이 등장합니다.
- 위의 코드에서 Comparison<DataHolder>는 DataHolder 타입의 두 객체를 비교하고 정수 값을 반환하여 정렬 순서를 결정하는 메서드의 대리자 타입입니다. (Comparison<T>라는 대리자(delegate) 타입 문법이 존재합니다.)
- new Comparison은 익명, 무명 메서드를 사용하여 Comparison<DataHolder> 타입의 대리자를 생성하였습니다.
익명, 무명 메서드(Anonymous Method)란?
- 익명, 무명 메서드(Anonymous Method): 이름이 없는 메서드를 정의할 수 있는 기능을 제공하는 C#의 구문입니다.
- 코드에서 메서드를 간단히 정의하고 사용할 수 있게 하며 대리자(delegate)와 함께 사용합니다.
- 짧은 코드로 간단한 메서드를 정의할 수 있습니다.
위에서 delegate를 통해 Comparison<DataHolder> 타입의 대리자를 생성하였고 DataHolder의 좌변과 우변을 비교하고 정수값을 반환합니다. 여기서 익명 메서드의 본문은
return _lhs.i.CompareTo(_rhs.i);
이 코드가 됩니다ㅣ int 타입의 i값을 가진 좌변과 우변을 DataHolder.cs의 Compare.To로 비교한 값을 반환한다는 뜻입니다. 이제 foreach()문을 해석합니다.
foreach (DataHolder data in dataHolderList)
{
//Console.WriteLine("{0} / {1} / {2}", data.str, data.i, data.f);
Console.WriteLine(data.ToString());
}
DataHolder 타입의 data 요소를 참조하는 변수를 넣어놓은 dataHolderList를 순회, 반복하라는 뜻입니다.
주석처리한 출력문의 출력값은 처음에 초기화 시켰던
{A} / {10} / {10.1}
{B} / {2} / {0.2} 가 출력됩니다.
밑의 출력문은 DataHolding.cs의 ToString() 메서드를 사용한 것으로
public override string ToString()
{
return string.Format("< {0} / {1} / {2} >", str, i, f);
}
이를 이용합니다. override는 덮어쓴다는 표현이라고 설명했었죠? 그에 따라. 위의 출력값과 같은
{A} / {10} / {10.1}
{B} / {2} / {0.2} 가 출력됩니다.
끝
'프로그래밍 C#언어' 카테고리의 다른 글
대리자(delegate) (0) | 2024.09.10 |
---|---|
템플릿(Template) (1) | 2024.09.09 |
상속3 (0) | 2024.09.09 |
상속2 (5) | 2024.09.05 |
상속 (Inheritance) (0) | 2024.08.30 |