이번에는 말로만 듣던 C#의 제네릭에 관해서 이야기해볼까 한다.
우선 제네릭은 타입과 상관없이 여러 상황에 같은 클래스, 메서드를 적용시키기 위한 편리한 방법 중 하나이다.
예시를 보는 것이 이해에 도움이 될 것이다.
1. 제네릭 클래스
using System;
public class GenericList<T>
{
private T[] items;
private int count;
public GenericList(int size)
{
items = new T[size];
count = 0;
}
public void Add(T item)
{
if (count < items.Length)
{
items[count++] = item;
}
else
{
throw new InvalidOperationException("리스트가 가득 찼습니다.");
}
}
public T Get(int index)
{
if (index >= 0 && index < count)
{
return items[index];
}
throw new IndexOutOfRangeException("유효하지 않은 인덱스입니다.");
}
}
public class Program
{
public static void Main(string[] args)
{
// GenericList<int> 인스턴스 생성
GenericList<int> intList = new GenericList<int>(5);
// 정수 값 추가
intList.Add(10);
intList.Add(20);
intList.Add(30);
// 값 가져오기
Console.WriteLine("인덱스 0의 값: " + intList.Get(0)); // 10
Console.WriteLine("인덱스 1의 값: " + intList.Get(1)); // 20
Console.WriteLine("인덱스 2의 값: " + intList.Get(2)); // 30
// 리스트의 크기를 초과하여 추가 시도 (예외 발생)
try
{
intList.Add(40);
intList.Add(50);
intList.Add(60); // 이 줄에서 예외 발생
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
}
}
제네릭 클래스는 위와 같이 클래스를 임시타입으로 정의하고
그 임시타입을 이용해서 여러 메서드나 변수를 정의 가능하다.
이렇게 만든 타입에 상관없이 동작하는 여러 함수들은 Program 부분처럼
타입만 넣어주면 간단히 사용이 가능해진다.
클래스를 먼저 정의해서 그 안에 여러 가지 변수나 메서드를 임시 타입으로 정의하고,
실행문에서 간단히 타입을 설정해 임의로 빠르게 사용이 가능하다고 해석 가능하다.
2. 제네릭 메서드
using System;
public class Program
{
// 제네릭 메서드 정의
public static T GetMax<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
public static void Main(string[] args)
{
// int 타입 비교
int maxInt = GetMax(10, 20);
Console.WriteLine("최대 정수: " + maxInt); // 출력: 최대 정수: 20
// double 타입 비교
double maxDouble = GetMax(10.5, 20.3);
Console.WriteLine("최대 실수: " + maxDouble); // 출력: 최대 실수: 20.3
// string 타입 비교
string maxString = GetMax("apple", "banana");
Console.WriteLine("최대 문자열: " + maxString); // 출력: 최대 문자열: banana
}
}
이건 윗부분이 중요한데, 제네릭 메서드는 마치 함수처럼 정의된다.
Where 부분은 무시하고 보자. 임시 타입 T를 기반으로 정의되었다.
제네릭 클래스는 클래스를 만들고 그걸 실행 클래스와 연결하는 방식이라면
이건 실행 클래스 안에서 제네릭 방식을 똑같이 메서드로 구현한 방법이다.
굳이 다른 클래스를 만들고 이어 줄 필요가 없기 때문에 같은 기능을 하면서도
함수 정의만으로 간단히 사용할 수 있다. 제네릭으로 사용할 메서드나 변수가 많다면
클래스를 따로 만드는 것이 유지 관리에 용이하겠으나 1개, 2개만 만들 거라면 제네릭
메서드를 사용하는 것이 조금 더 유리할 수 있겠다.
3. (형식) 제약 조건 (Where)
Where 부분이 어떤 역할과 의미를 담고 있는 지도 궁금할 것이다. 그 부분만 따로 설명하겠다.
Where은 제약 조건이라고 불리며 제네릭의 본질인 "타입에 상관없는" 특성의 단점을 보완한다.
만약에 더하기를 만들었는데 문자 2개가 나온다면 실행이 되긴 하겠지만 원하던 것이 아닐 것이다.
이런 경우에 "계산 가능한 타입"이라는 조건이 있어야만 정상적으로 작동하면서도 타입의
유연성을 충족할 수 있을 것이다. 이런 조건을 붙이기 위해 존재하는 것이 제약 조건, Where 구문이다.
이 밖에도 몇 개 더 있지만 이 정도만 알아도 꽤나 많은 상황에 대처 가능할 것이다.
각각의 더 자세한 설명은 사진 아래 링크를 참고하거나 직접 알아보길 바란다.
4. 제네릭 인터페이스
using System;
using System.Collections.Generic;
// 제네릭 인터페이스 정의
public interface IRepository<T>
{
void Add(T item);
T Get(int id);
}
// 제네릭 인터페이스 구현
public class InMemoryRepository<T> : IRepository<T>
{
private readonly List<T> _items = new List<T>();
private int _nextId = 0;
public void Add(T item)
{
_items.Add(item);
_nextId++;
}
public T Get(int id)
{
if (id < 0 || id >= _items.Count)
{
throw new IndexOutOfRangeException("유효하지 않은 ID입니다.");
}
return _items[id];
}
}
public class Program
{
public static void Main(string[] args)
{
IRepository<string> stringRepository = new InMemoryRepository<string>();
stringRepository.Add("Hello");
stringRepository.Add("World");
Console.WriteLine(stringRepository.Get(0)); // 출력: Hello
Console.WriteLine(stringRepository.Get(1)); // 출력: World
IRepository<int> intRepository = new InMemoryRepository<int>();
intRepository.Add(1);
intRepository.Add(2);
Console.WriteLine(intRepository.Get(0)); // 출력: 1
Console.WriteLine(intRepository.Get(1)); // 출력: 2
}
}
마지막으로 제네릭 인터페이스다.
https://alpaca-code.tistory.com/250 (interface)
먼저 인터페이스에 관한 설명을 듣고 오면 편한데, 간단하게 설명하자면 같은 기능을 공유하는
클래스들을 위해서 틀을 잡아놓는 개념이라고 보면 된다. 이제 여기서 제네릭이 추가되면
타입에 상관없이 클래스들에 동일한 메서드를 미리 정의해 줄 수 있게 되는데,
이걸 제네릭 인터페이스라고 호칭한다.
타입에 상관없이 인터페이스가 구현된 클래스의 메서드를 정의할 수 있는 기술이라고 보면 된다.
여기까지 C#의 제네릭에 관해서 알아봤다. 제네릭, 많이 들어봤지만
사용한 적은 단 한 번도 없다. 확실히 순수 언어 공부도 해야겠다고 마음먹었으며
사실 책에서 간단하게 알려주고 이해하라고 말한 개념이라 추가로 찾아보고
이렇게 글로 남겨 미래의 나에게 지식을 유지하도록 돕는 것이다.
확실히 같은 카테고리의 여러 책들을 읽는 것이 지식에 도움이 되는 것 같다.
이렇게 하나하나 기술을 익혀서 최적화가 잘 되어있는 게임이 완성되길 빈다.
이상으로 도움이 되었길 바라며,
끝.
'게임 개발 > C#' 카테고리의 다른 글
C# 코드 생략 기법 - 메서드 체이닝(Method Chaining) (0) | 2024.12.11 |
---|---|
C# Interface의 기능, 사용법 (0) | 2024.11.24 |
C# 에서 문자열을 반복하는 가장 간단한 방법 - new string (0) | 2024.11.03 |
이스케이프(escape) - 특수문자를 문자열로 바꾸는 법, 출력하는 법. (0) | 2024.08.01 |
C#에서 ?와 ??의 의미, null을 판단하는 방법(null 조건부 연산자, null 병합 연산자) (2) | 2024.07.30 |
댓글