Search

이것이 C#이다: 11장 - 일반화 프로그래밍

서론

C# 학습 여정에서 일반화 프로그래밍(Generic Programming)은 중요한 이정표입니다. 일반화 프로그래밍은 데이터 형식을 일반화하여 코드의 재사용성을 높이고 타입 안정성을 보장하는 프로그래밍 패러다임입니다. 이는 효율적이고 유지보수가 용이한 코드를 작성하는 데 필수적인 개념으로, C# 개발자로서 반드시 숙지해야 할 주제입니다.
본 포스트에서는 '이것이 C#이다' 책의 11장을 바탕으로, 일반화 프로그래밍의 핵심 개념과 C#에서의 구현 방법을 상세히 다루겠습니다.

본론

1. 일반화 프로그래밍이란?

일반화 프로그래밍은 데이터 형식을 일반화하는 프로그래밍 패러다임입니다. C#에서는 이를 '제네릭(Generic)'이라고 부릅니다. 일반화의 핵심은 코드의 재사용성을 높이고, 형식 안정성을 보장하며, 성능을 향상시키는 데 있습니다.
예를 들어, 정수형 배열을 정렬하는 메소드와 문자열 배열을 정렬하는 메소드가 필요하다고 가정해봅시다. 일반화를 사용하지 않으면 두 개의 별도 메소드를 작성해야 하지만, 일반화를 사용하면 하나의 메소드로 두 가지 경우를 모두 처리할 수 있습니다.

2. 일반화 메소드 (Generic Methods)

일반화 메소드는 데이터 형식을 매개변수화하여 다양한 타입에 대해 동작할 수 있는 메소드입니다. 이를 통해 코드 중복을 줄이고 타입 안정성을 높일 수 있습니다.
예를 들어, 두 변수의 값을 교환하는 Swap 메소드를 일반화하여 구현해 보겠습니다:
public static void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; }
C#
복사
이 메소드는 어떤 타입의 변수든 교환할 수 있습니다:
int x = 5, y = 10; Swap<int>(ref x, ref y); string s1 = "Hello", s2 = "World"; Swap<string>(ref s1, ref s2);
C#
복사

3. 일반화 클래스 (Generic Classes)

일반화 클래스는 클래스의 멤버가 특정 타입에 종속되지 않도록 합니다. 이는 다양한 데이터 타입을 처리할 수 있는 유연한 클래스 설계를 가능케 합니다.
간단한 Stack<T> 클래스를 구현해 보겠습니다:
public class Stack<T> { private T[] _items; private int _top; public Stack(int capacity) { _items = new T[capacity]; _top = -1; } public void Push(T item) { if (_top == _items.Length - 1) throw new StackOverflowException(); _items[++_top] = item; } public T Pop() { if (_top == -1) throw new InvalidOperationException("Stack is empty"); return _items[_top--]; } }
C#
복사
이제 이 Stack<T>는 어떤 타입의 데이터도 저장할 수 있습니다:
var intStack = new Stack<int>(10); intStack.Push(5); intStack.Push(10); var stringStack = new Stack<string>(10); stringStack.Push("Hello"); stringStack.Push("World");
C#
복사

4. 형식 매개변수 제약 (Type Parameter Constraints)

때로는 일반화 타입에 특정 조건을 부여해야 할 필요가 있습니다. 이를 위해 C#은 형식 매개변수 제약을 제공합니다.
예를 들어, 비교 가능한 타입만을 허용하는 메소드를 작성할 수 있습니다:
public static T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; }
C#
복사
이 메소드는 IComparable<T> 인터페이스를 구현한 타입에 대해서만 동작합니다:
int maxInt = Max(5, 10); // 10 string maxString = Max("apple", "banana"); // "banana"
C#
복사

5. 일반화 컬렉션 (Generic Collections)

C#은 System.Collections.Generic 네임스페이스에서 다양한 일반화 컬렉션을 제공합니다. 이들은 타입 안정성과 성능 면에서 비일반화 컬렉션보다 우수합니다.
주요 일반화 컬렉션의 사용 예:
// List<T> List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; numbers.Add(6); // Dictionary<TKey, TValue> Dictionary<string, int> ages = new Dictionary<string, int> { ["Alice"] = 30, ["Bob"] = 25 }; ages["Charlie"] = 35; // Queue<T> Queue<string> tasks = new Queue<string>(); tasks.Enqueue("Task 1"); tasks.Enqueue("Task 2"); string nextTask = tasks.Dequeue(); // "Task 1" // Stack<T> Stack<double> prices = new Stack<double>(); prices.Push(10.99); prices.Push(20.49); double lastPrice = prices.Pop(); // 20.49
C#
복사

6. IEnumerable<T>를 이용한 foreach 구현

IEnumerable<T> 인터페이스를 구현하면 foreach 문을 사용할 수 있는 일반화 컬렉션을 만들 수 있습니다. 이는 효율적인 반복 처리를 가능케 합니다.
간단한 예시:
public class MyCollection<T> : IEnumerable<T> { private List<T> _items = new List<T>(); public void Add(T item) { _items.Add(item); } public IEnumerator<T> GetEnumerator() { return _items.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }
C#
복사
이제 이 컬렉션은 foreach 문에서 사용할 수 있습니다:
var collection = new MyCollection<string>(); collection.Add("Item 1"); collection.Add("Item 2"); foreach (var item in collection) { Console.WriteLine(item); }
C#
복사

결론

일반화 프로그래밍은 C#에서 코드의 재사용성, 타입 안정성, 그리고 성능을 크게 향상시킬 수 있는 강력한 도구입니다. 개인적으로 팀 프로젝트를 진행하면서 일반화의 중요성을 깨달았습니다. 특히 게임 개발에서 다양한 게임 오브젝트나 컴포넌트를 다룰 때 일반화 프로그래밍의 이점이 두드러집니다.
일반화 프로그래밍은 처음에는 이해하기 어려울 수 있지만, 꾸준한 학습과 실습을 통해 점차 익숙해질 수 있습니다. 이를 통해 더 효율적이고 유지보수가 쉬운 코드를 작성할 수 있게 될 것입니다.

참고 자료

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