Search

이것이 C#이다: 10장 - 배열과 컬렉션 그리고 인덱서 (2/2)

서론

이전 포스트에서 C#의 배열에 대해 살펴보았습니다. 이번에는 더 다양하고 유연한 데이터 구조인 컬렉션, 그리고 객체를 배열처럼 다룰 수 있게 해주는 인덱서에 대해 알아보겠습니다. 또한, foreach 문을 사용할 수 있게 해주는 IEnumerable 인터페이스에 대해서도 다루어 보겠습니다.

본론

1. 주요 컬렉션 클래스 살펴보기

컬렉션은 데이터의 집합을 다루는 클래스들을 통칭합니다. C#은 다양한 종류의 컬렉션을 제공하며, 각각 고유한 특성과 사용 사례를 가지고 있습니다.

1.1 ArrayList: 동적 배열의 구현체

ArrayList는 크기가 동적으로 조절되는 배열과 같은 컬렉션입니다.
ArrayList list = new ArrayList(); list.Add(1); list.Add("Two"); list.Add(3.0); Console.WriteLine(list[1]);// 출력: Two
C#
복사
ArrayList의 장점은 다양한 타입의 객체를 저장할 수 있다는 것입니다. 하지만 이는 동시에 타입 안정성을 해칠 수 있어, 가능하면 제네릭 List<T>를 사용하는 것이 좋습니다.

1.2 Queue: 선입선출(FIFO) 구조

Queue는 데이터를 순서대로 처리할 때 유용합니다.
Queue queue = new Queue(); queue.Enqueue("First"); queue.Enqueue("Second"); queue.Enqueue("Third"); Console.WriteLine(queue.Dequeue());// 출력: First
C#
복사
Queue는 작업 대기열이나 버퍼 구현에 자주 사용됩니다.

1.3 Stack: 후입선출(LIFO) 구조

Stack은 가장 최근에 추가된 항목을 먼저 처리해야 할 때 유용합니다.
Stack stack = new Stack(); stack.Push("First"); stack.Push("Second"); stack.Push("Third"); Console.WriteLine(stack.Pop());// 출력: Third
C#
복사
Stack은 함수 호출 관리, 실행 취소 기능 구현 등에 사용됩니다.

1.4 Hashtable: 키-값 쌍의 컬렉션

Hashtable은 키를 통해 빠르게 값을 검색할 수 있는 구조입니다.
Hashtable hash = new Hashtable(); hash["key1"] = "value1"; hash["key2"] = "value2"; Console.WriteLine(hash["key1"]);// 출력: value1
C#
복사
Hashtable은 빠른 검색이 필요한 대량의 데이터를 다룰 때 유용하지만, 제네릭 Dictionary<TKey, TValue>를 사용하는 것이 더 타입 안전하고 성능이 좋습니다.

2. 컬렉션 초기화: 간결하고 명확하게

C#은 컬렉션을 초기화하는 여러 방법을 제공합니다.
// 배열을 이용한 초기화 ArrayList list1 = new ArrayList(new int[] { 1, 2, 3 }); // 컬렉션 초기자를 이용한 초기화 ArrayList list2 = new ArrayList { 1, 2, 3 }; // 딕셔너리 초기자를 이용한 Hashtable 초기화 Hashtable hash = new Hashtable { ["Key1"] = "Value1", ["Key2"] = 123 };
C#
복사
컬렉션 초기자는 코드를 더 간결하고 읽기 쉽게 만들어줍니다. 특히 복잡한 객체 구조를 초기화할 때 유용합니다.

3. 인덱서: 객체를 배열처럼

인덱서는 객체를 배열처럼 인덱스로 접근할 수 있게 해주는 특별한 형태의 프로퍼티입니다. 사용자 정의 클래스에서 인덱서를 구현하면, 해당 객체를 마치 배열처럼 사용할 수 있습니다.
public class SampleCollection { private string[] arr = new string[100]; public string this[int i] { get { return arr[i]; } set { arr[i] = value; } } } SampleCollection collection = new SampleCollection(); collection[0] = "Item"; Console.WriteLine(collection[0]);// 출력: Item
C#
복사
인덱서는 클래스의 내부 구현을 숨기면서도 직관적인 인터페이스를 제공할 수 있게 해줍니다. 예를 들어, 데이터베이스 레코드나 게임에서의 인벤토리 시스템 등을 구현할 때 유용하게 사용될 수 있습니다.

4. foreach와 IEnumerable: 반복의 추상화

foreach 문은 IEnumerable 인터페이스를 구현한 모든 객체에 대해 사용할 수 있습니다. 이 인터페이스는 GetEnumerator() 메서드 하나만을 가지고 있으며, 이 메서드는 IEnumerator 인터페이스를 반환합니다.
public class MyCollection : IEnumerable { private int[] data = { 1, 2, 3, 4, 5 }; public IEnumerator GetEnumerator() { foreach (int item in data) { yield return item; } } } MyCollection collection = new MyCollection(); foreach (int item in collection) { Console.WriteLine(item); }
C#
복사
yield return 문을 사용하면 복잡한 IEnumerator 구현 없이도 간단하게 열거 가능한 객체를 만들 수 있습니다. 또한 열거자의 현재 위치를 기억하고, 다음 요청 시 이어서 실행할 수 있게 해줍니다. 이는 데이터를 순차적으로 처리해야 하는 많은 상황에서 유용합니다.

결론

이번 포스트에서는 C#의 배열과 컬렉션, 그리고 관련된 강력한 기능들에 대해 알아보았습니다. 이러한 도구들은 데이터를 효과적으로 관리하고 조작하는 데 필수적입니다.
개인적으로 IEnumerable과 yield 키워드의 개념을 처음 접했을 때 어려움을 겪었지만, 이를 이해하고 나니 데이터 스트림을 다루는 새로운 관점을 얻을 수 있었습니다.
C#의 컬렉션과 관련 기능들은 단순히 데이터를 저장하는 것을 넘어, 효율적인 알고리즘 구현과 깔끔한 코드 작성의 기반이 됩니다. 이러한 도구들을 잘 활용하면, 더 나은 성능과 유지보수성을 가진 애플리케이션을 개발할 수 있을 것입니다.

참고 자료

박상현, "이것이 C#이다", 한빛미디어, 2023