[C#]컬렉션(Collection)
서론
C++에 STL이 있다면, C#에서는 데이터 원소들을 저장, 관리하는 컬렉션이 있습니다. 이번 글에서는 C#의 데이터 저장과 처리를 위한 자료구조 컬렉션에 대해서 다뤄보려 합니다.
제네릭/논제네릭 컬렉션 (Generic/Non-Generic Collection)
이전에 [C#] 제네릭(Generic)에 관한 글을 쓴 적이 있습니다. 컬렉션을 알기 전에, 컬렉션에 두 가지 타입이 있다는 걸 먼저 말하고 싶습니다. 컬렉션이 Generic/Non-Genrice이냐는 컬렉션에 담기는 데이터(원소) 타입이 명시적 인지 아닌지에 따라 다릅니다.
다른 부가적인 사항들 말고, 핵심적인 것 하나만 알면 됩니다. 데이터의 타입이 명시적이냐는 겁니다. 아래 예시를 보겠습니다.
//Generic collection : System.Collections.Generic.List<T>
List<int> list = new List<int>(); // int 타입의 데이터를 담을 수 있는 동적 배열 기반의 컬렉션
list.Add(10); //ok
list.Add("ten"); //error
//Non-Generic collection : System.Collections.ArraryList
ArraryList arraryList = new ArraryList(); //object 타입의 데이터를 담을 수 있는 동적 배열 기반의 컬렉션
arraryList.Add(10); //ok
arraryList.Add("ten"); //ok
List <T>는 제네릭 컬렉션입니다. 제네릭 타입으로 T표식이 달려 있다는 것은, 어떤 타입인지 모르지만 한 가지 타입만을 명시적으로 받겠다는 뜻이죠. 선언 시 타입만 정해주면, 어떤 타입이든 상관없지만 오로지 그 타입만을 처리해야 합니다.
반대로 논제네릭 컬렉션인 ArraryList는 특정 타입이 명시적으로 정해져 있지 않습니다. 즉 object타입을 받는다는 뜻이며, 하나의 컬렉션에 여러 타입의 원소를 담을 수 있다는 뜻이죠. 이는 원소를 object 타입으로 받아(Boxing)서 사용할 때 명시적으로 캐스팅해(Unboxing) 사용할 수 있기 때문입니다.
이 외에 사실상 ArraryList와 List의 경우처럼 같은 자료구조의 컬렉션이지만 제네릭/논제네릭으로만 나뉘기에 필요에 따라 사용하면 됩니다. 이후에 몇 가지 주요 컬렉션 종류를 알아보겠습니다.
컬렉션 몇가지 종류
Generic collection | Non-Generic collection | Data struct |
List<T> | Arrary, ArraryList | 동적 배열, 랜덤 접근 가능 |
Dictionary<TKey, TValue>
|
Hashtable | 해시 테이블, 랜덤 접근 가능 |
Queue<T> | Queue | 큐, FIFO |
Stack<T> | Stack | 스택, LIFO |
SortedList<TKey, TValue> | SortedList | Map ADT(Abstract data type), 배열 |
HashSet<T>, SortedSet<T> | - | 집합(Set), 정렬 여부 차이 |
LinkedList<T> | - | 연결 리스트 |
기반 자료구조가 동일하고 제네릭 컬렉션 중, 논제네릭을 지원하는지 여부의 차이만 있을 뿐 큰 차이점은 없다고 보면 됩니다. 전부를 나열한 것은 아니지만 많이 보이는 컬렉션들을 소개해 봤습니다.
마무리
컬렉션과 기반 자료구조와 제네릭의 여부를 아는게 중요한 것 같습니다. 한 가지 예를 들어 보겠습니다. 다음과 같은 상황에서는 어떤 컬렉션을 선택하는 게 좋을까요?
- 처리해야 할 원소의 데이터 타입은 선언시 명시적으로 정합니다.
- 순차적으로 탐색하는 경우도 종종 있고, 간혹 랜덤 액세스가 필요합니다.
- 데이터의 중복을 허용합니다.
- 삽입과 삭제는 빈번하게 일어나지 않습니다.
각 항목을 하나하나 살펴보겠습니다.
- 처리해야 할 원소의 데이터 타입은 선언 시 명시적으로 정합니다.
=> 기능은 일반화가 되어 있지만, 타입이 미정인 상태입니다. 사용자가 사용할 때(선언 시) 정해주면 되므로, 제네릭 타입의 컬렉션 중 선택합니다. - 순차적으로 탐색하는 경우도 종종 있고, 간혹 랜덤 액세스가 필요합니다.
=> 랜덤 액세스가 되어야 하고, 순차 탐색이 가능해야 합니다. - 데이터의 중복을 허용합니다.
=> 정렬된 상태나, 데이터의 중복을 할 수 없는 컬렉션은 제거합니다. - 삽입과 삭제는 빈번하게 일어나지 않습니다.
=> 컬렉션 중 삽입과 삭제에 드는 시간 복잡도가 다릅니다. 빈번하지 않으므로 크게 고려하지 않아도 됩니다.
살펴본 결과 사용 하기에 적절한 컬렉션은 List <T>, Dictionary <TKey, TValue> 정도가 있겠네요. 어디까지나 예시입니다. 이후에 여러 가지를 고려해 볼 수 있습니다. 몇 가지 예시를 보겠습니다.
- 랜덤 액세스를 인덱스로 할 것인지, 유니크한 key 값으로 할 것인지?
=> 구현 복잡도와 가독성 등 코드 스타일과 설계 목적에 맞게 선택하면 될 것입니다. - 순차적으로 탐색할 때, Dictionary의 경우는 일반 for문 말고 foreach를 많이 사용합니다.
=> foreach는 가비지가 생기는 작업입니다. 부하가 있을 수 있겠지만, 추가 작업으로 for문 사용도 가능합니다. - 삽입과 삭제가 빈번하지는 않지만 삭제 시에는 Dictionary가 O(1)로 List의 O(n)보다는 빠릅니다.
=> 삭제가 빈번할 수 있는 확장성이 고려된다면, 생각해 볼 수 있겠네요.
이처럼 딱 정해진 답은 없습니다. 하지만 어떤 상황이냐에 적절한 선택을 할 수 있는지는 얼마나 알고 있냐가 큰 관건인 것 같습니다.