C# 일반화 프로그래밍

김민구·2025년 5월 27일
0

C#

목록 보기
21/31

1. 일반화(제네릭) 프로그래밍이란?

C#에서 일반화(제네릭) 프로그래밍은 하나의 코드가 여러 데이터 형식에 맞춰 동작할 수 있도록 지원하는 기능입니다. 이는 프로그래머가 작성하는 코드의 생산성에 아주 중요한 영향을 미치는 내용이라고 합니다. 특히 일반화 컬렉션(Generic Collection)은 꼭 익혀둘 필요가 있습니다.

2. 왜 일반화(제네릭)을 사용할까요?

일반화(제네릭)이 없다면, 우리는 다양한 데이터 형식에 대해 동일한 로직을 수행하는 함수나 클래스를 만들 때 각 형식별로 코드를 따로 작성해야 합니다.

예를 들어, 정수형 배열을 복사하는 CopyArray 함수와 문자열 배열을 복사하는 CopyArray 함수를 만든다고 할 때, 함수 이름은 같지만 매개변수와 내부 코드가 동일한 구조를 가지고 있더라도 형식이 다르다는 이유로 두 개의 함수를 만들어야 합니다.

// int 배열 복사 (비 제네릭)
void CopyArray(int[] source, int[] target)
{
    for (int i = 0; i < source.Length; i++)
        target[i] = source[i];
}

// string 배열 복사 (비 제네릭)
void CopyArray(string[] source, string[] target)
{
    for (int i = 0; i < source.Length; i++)
        target[i] = source[i];
}

클래스의 경우도 마찬가지입니다. 정수형 배열을 다루는 Array_Int 클래스와 double형 배열을 다루는 Array_Double 클래스는 내부적으로 유사한 구조를 가짐에도 불구하고 데이터 형식 때문에 별도로 정의해야 합니다.

class Array_Int // 비 제네릭
{
    private int[] array;
    public int GetElement(int index) { return array[index]; }
}

class Array_Double // 비 제네릭
{
    private double[] array;
    public double GetElement(int index) { return array[index]; }
}

이러한 반복적인 코드 작성을 피하고 타입 안전성(잘못된 형식의 데이터가 들어가는 것을 막아주는 것)을 확보하기 위해 일반화(제네릭)이 사용됩니다.

3. 일반화(제네릭) 메서드 (Generic Method)

일반화(제네릭) 메서드는 메서드의 매개변수나 반환 타입에 제네릭 형식 매개변수(Type Parameter)를 사용하는 메서드입니다. 형식 매개변수는 보통 <T>와 같이 꺾쇠 괄호 안에 표현합니다.

위에서 본 CopyArray 함수를 제네릭으로 만들면 하나의 함수로 여러 형식의 배열을 복사할 수 있습니다.

// 제네릭 메서드
static void CopyArray<T>(T[] source, T[] target)
{
    for (int i = 0; i < source.Length; i++)
        target[i] = source[i];
}

이 제네릭 CopyArray 메서드는 int 형식으로도 호출할 수 있고, string 형식으로도 호출할 수 있습니다. 이때 형식 매개변수 T는 컴파일 시점에 실제 사용되는 형식(예: int, string)으로 결정됩니다.

4. 일반화(제네릭) 클래스 (Generic Class)

일반화(제네릭) 클래스는 클래스 정의에 하나 이상의 일반화(제네릭) 형식 매개변수를 사용하는 클래스입니다.

비 일반화(제네릭) 클래스 예시에서 보았던 Array_IntArray_Double처럼 특정 형식에 종속적인 클래스 대신, 제네릭을 사용하여 어떤 형식이든 담을 수 있는 Array_Generic<T> 클래스를 만들 수 있습니다.

class Array_Generic<T> // 제네릭 클래스
{
    private T[] array;
    // ... 기타 멤버 ...
    public T GetElement(int index) { return array[index]; }
}

이 클래스를 사용할 때는 구체적인 형식을 지정해줍니다.

Array_Generic<int> intArr = new Array_Generic<int>(); // int 형식 사용
Array_Generic<double> dblArr = new Array_Generic<double>(); // double 형식 사용

예시 소스에서는 일반화(제네릭) 클래스 MyList<T>를 직접 구현하는 코드를 보여주며, 내부적으로 T 형식의 배열을 가지고 요소를 추가하고 가져오는 방법을 보여줍니다.

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

때로는 일반화(제네릭) 형식 매개변수에 특정 조건을 부여해야 할 필요가 있습니다. 예를 들어, 특정 메서드를 호출하거나, 인터페이스를 구현하거나, 특정 클래스를 상속받는 형식만 제네릭 형식으로 사용하도록 제한할 수 있습니다. 이를 형식 매개변수 제약이라고 합니다. where 키워드를 사용하여 제약을 명시합니다.

  • where T : struct: T는 값 형식이어야 합니다.
  • where T : class: T는 참조 형식이어야 합니다.
  • where T : new(): T는 매개변수 없는 생성자가 있어야 합니다.
  • where T : BaseClass: T는 BaseClass 또는 그 파생 클래스여야 합니다.
  • where T : IMyInterface: T는 IMyInterface를 구현해야 합니다.

예시 소스에서는 StructArray<T> 클래스에 where T : struct 제약을 사용하여 값 형식만 받도록 하거나, RefArray<T> 클래스에 where T : class 제약을 사용하여 참조 형식만 받도록 하는 것을 보여줍니다. 또한 CreateInstance<T> 메서드에 where T : new() 제약을 사용하여 매개변수 없는 생성자가 있는 형식만 인스턴스 생성에 사용할 수 있도록 하는 예시를 보여줍니다.

6. .NET에서 제공하는 일반화 컬렉션 (Generic Collections)

.NET 프레임워크는 다양한 제네릭 컬렉션 클래스를 제공하여 데이터 관리를 편리하게 합니다. 이들은 특정 형식의 요소만 담을 수 있어 타입 안전하며 성능도 뛰어납니다.

  • List<T>: 크기가 동적으로 변하는 배열과 같은 컬렉션입니다. 요소를 추가(Add), 삽입(Insert), 제거(RemoveAt)하는 등의 작업을 할 수 있습니다.
  • Queue<T>: 선입선출(FIFO - First-In, First-Out) 구조의 컬렉션입니다. 요소를 추가할 때는 Enqueue(), 꺼낼 때는 Dequeue()를 사용합니다.
  • Stack<T>: 후입선출(LIFO - Last-In, First-Out) 구조의 컬렉션입니다. 요소를 추가할 때는 Push(), 꺼낼 때는 Pop()을 사용합니다.
  • Dictionary<TKey, TValue>: 키(Key)와 값(Value) 쌍으로 데이터를 저장하는 컬렉션입니다. 키를 통해 값에 빠르게 접근할 수 있습니다.

7. foreach를 사용할 수 있는 일반화 클래스 만들기 (IEnumerable<T> 구현)

직접 만든 제네릭 클래스를 foreach 문과 함께 사용하려면 System.Collections.Generic 네임스페이스에 있는 IEnumerable<T> 인터페이스와 IEnumerator<T> 인터페이스를 구현해야 합니다. 소스에서는 MyList<T> 클래스가 이 두 인터페이스를 구현하여 foreach 반복이 가능하게 만드는 과정을 보여줍니다. IEnumerable<T>GetEnumerator() 메서드가 IEnumerator<T> 객체를 반환하고, IEnumerator<T>MoveNext(), Current, Reset(), Dispose() 등의 멤버를 구현하여 컬렉션의 요소를 순회할 수 있게 됩니다.

profile
C#, Unity

0개의 댓글