서론
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