반응형
제네릭 ( Generic )
데이터 형식을 일반화하는 프로그래밍 기법
코드를 더 유연하고 재사용 가능하게 만들어줌
주요특징
타입 안전성 보장
코드 재사용성 향상
성능 최적화 (박싱/언박싱 방지)
컴파일 시점 타입 검사
선언방법
제네릭 클래스
public class DataContainer<T>
{
private T data;
public void SetData(T newData)
{
data = newData;
}
public T GetData()
{
return data;
}
}
제네릭 메서드
public T ProcessItem<T>(T item)
{
return item;
}
제네릭 인터페이스
public interface IRepository<T>
{
void Add(T item);
T Get(int id);
void Delete(T item);
}
제한조건
제네릭 타입 매개변수에 대해 "이러한 조건을 가진 타입만 사용할 수 있다" 라고 제한하는 것
제한 조건의 기본문법 및 예시
where T : 제한조건
// T는 class 타입만 받을 수 있다는 제한
public class MyClass<T> where T : class
// T는 IComparable을 구현한 타입만 받을 수 있다는 제한
public class MyClass<T> where T : IComparable
쉽게 말하면 "이 놀이기구는 키 140cm 이상만 탈 수 있다" 라는 제한과 비슷한 개념이다
이 제네릭은 이런 조건을 만족하는 타입만 사용할 수 있다라고 정의하는 것임
제한 조건 종류
값 형식 ( struct ) 제한
public class DataProcessor<T> where T : struct
{
// T는 int, float, bool 등의 값 형식만 가능
public T Process(T item)
{
if (item != null) {
// 참조 타입 관련 처리
}
return item;
}
}
- null 할당 불가
- 값 타입만 사용 가능
- int, float, bool 등의 기본 값 형식 사용 가능
참조 형식 ( class ) 제한
public class ObjectHandler<T> where T : class
{
// T는 class, interface, delegate, array 형식만 가능
public T Process(T item)
{
// 값 타입은 null이 될 수 없으므로 null 체크 불필요
return item;
}
}
- null 할당 가능
- 참조 타입의 메서드/프로퍼티 사용 가능
- 클래스, 인터페이스, 델리게이트, 배열 사용 가능
매개변수 없는 생성자 (new()) 제한
public class Factory<T> where T : new()
{
public T CreateNew()
{
return new T(); // 기본 생성자로 인스턴스 생성 가능
}
}
- 매개변수 없는 생성자 필수
- 다른 제한조건과 함께 사용 시 항상 마지막에 위치
기본 클래스 (base class) 제한
public class EntityManager<T> where T : MonoBehaviour
{
// T는 MonoBehaviour나 그 자식 클래스만 가능
public T GetComponent(GameObject obj)
{
return obj.GetComponent<T>();
}
}
- 지정된 클래스나 그 자식 클래스만 사용 가능
- 해당 클래스의 메서드/프로퍼티 사용 가능
특정 인터페이스 제한
public interface IDataProcessor
{
void ProcessData();
}
// ==================================================
public class DataManager<T> where T : IDataProcessor
{
// T는 IDataProcessor 인터페이스를 구현한 타입만 가능
public void ExecuteProcess(T processor)
{
processor.ProcessData();
}
}
- 지정된 인터페이스를 구현한 타입만 사용 가능
- 여러 인터페이스 제한조건 동시 사용 가능
다중 제한 조건
// 여러 제한조건을 동시에 적용
public class AdvancedManager<T> where T : class, IDataProcessor, new()
{
public T CreateAndProcess()
{
T item = new T();
item.ProcessData();
return item;
}
}
- 여러 개의 제한 조건을 동시에 적용 가능
- 콤마(,)로 구분하여 나열
- new() 제한자는 항상 마지막에 위치해야 함
- 클래스 제한자는 인터페이스보다 앞에 위치해야 함
제네릭 메서드의 제한 조건
// 메서드 레벨에서의 제한조건 적용
public static class Utility
{
public static void Process<T>(T item) where T : Component
{
item.gameObject.SetActive(true);
}
}
- 메서드 단위로 제한 조건 적용 가능
- 클래스의 제한 조건과 독립적으로 동작
- 메서드마다 다른 제한 조건 설정 가능
제한조건을 사용 하는 이유
타입 안정성 보장
컴파일 시점 오류 검사
의도한 동작 보장
코드의 명확성과 가독성 향상
잘못된 사용 방지
제네릭의 장점
성능향상
타입 안정성 광화
코드 유지보수 용이
제네릭을 사용하는 상황
컬렉션(List, Dictionary 등) 구현 시
유틸리티 함수 작성 시
데이터 저장소/캐시 구현 시
컴포넌트 참조 관리 시
범용 알고리즘 구현 시
활용 예제
범용적인 오브젝트 풀 매니저
public class ObjectPool<T> where T : Component
{
private Queue<T> pool = new Queue<T>();
private T prefab;
private Transform parent;
public ObjectPool(T prefab, int initialSize, Transform parent)
{
this.prefab = prefab;
this.parent = parent;
for (int i = 0; i < initialSize; i++)
{
CreateNewObject();
}
}
private void CreateNewObject()
{
T obj = GameObject.Instantiate(prefab, parent);
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
public T GetObject()
{
if (pool.Count == 0)
{
CreateNewObject();
}
T obj = pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}
public void ReturnObject(T obj)
{
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
사용 예시
public class BulletManager : MonoBehaviour
{
private ObjectPool<Bullet> bulletPool;
void Start()
{
bulletPool = new ObjectPool<Bullet>(bulletPrefab, 10, transform);
}
void Fire()
{
Bullet bullet = bulletPool.GetObject();
// 총알 발사 로직...
}
}
제약조건을 활용한 예제
// 컴포넌트에 대한 제약조건
public T GetComponentInChildren<T>(string childName) where T : Component
{
Transform child = transform.Find(childName);
return child?.GetComponent<T>();
}
// 인터페이스에 대한 제약조건
public class DamageableEntity<T> where T : IDamageable
{
private T entity;
public void ApplyDamage(float amount)
{
entity.TakeDamage(amount);
}
}
확장 메서드
public static class ListExtensions
{
public static T GetRandomItem<T>(this List<T> list)
{
if (list == null || list.Count == 0)
return default(T);
int randomIndex = Random.Range(0, list.Count);
return list[randomIndex];
}
}
// 사용 예시
List<string> items = new List<string>() { "Sword", "Shield", "Potion" };
string randomItem = items.GetRandomItem();
실제 개발에선 어디서 활용할까?
데이터 관리 시스템
이벤트 시스템
저장/로드 시스템
UI 관리 시스템
리소스 관리 시스템
제네릭이 이러한 시스템들에서 사용되는 핵심 이유는 "범용성" 이다
다양한 타입의 데이터/객체를 처리하거나 동일한 로직을 여러 타입에 적용해야 할 경우 등에만 사용한다
제네릭을 무차별적 사용 시 발생하는 문제
나쁜 예: 불필요하게 복잡한 제네릭 사용
public class GameManager<T, U, V>
where T : IPlayerData
where U : IGameState
where V : IScoreSystem
{
private T playerData;
private U gameState;
private V scoreSystem;
public void UpdateGame()
{
// 복잡한 타입 파라미터들로 인해 코드 가독성 저하
// 유지보수가 어려워짐
// 다른 개발자가 이해하기 어려움
}
}
좋은 예: 명확하고 단순한 구현
public class GameManager
{
private PlayerData playerData;
private GameState gameState;
private ScoreSystem scoreSystem;
public void UpdateGame()
{
// 명확하고 이해하기 쉬운 코드
}
}
코드의 복잡성이 증가하며 불필요한 타입 체크 및 캐스팅으로 인해 성능이 저하된다
또한 가독성이 떨어지고 디버깅이 어렵기 때문에 유지보수 또한 어려워진다
반응형
'유니티 > 문법' 카테고리의 다른 글
| [Unity] 유니티 C#) 코루틴(Coroutine) (1) | 2024.12.27 |
|---|---|
| [Unity] 유니티 C#) 네임스페이스 ( namespace ) (0) | 2024.12.26 |
| [Unity] 유니티 C#) 추상클래스와 가상메서드 (0) | 2024.11.27 |
| [Unity] 유니티 C#) 생성자 (0) | 2024.11.26 |
| [Unity] 유니티 C#) ref 와 out 키워드 (0) | 2024.11.25 |