반응형

제네릭(Generics)이란?

개발하다 보면 “같은 로직인데 타입만 다르게” 쓰고 싶은 순간이 자주 온다.
예를 들어 int도 담고 싶고 string도 담고 싶고, 

Unity에서는 Bullet, Enemy, Effect 같은 서로 다른 타입을 같은 구조로 관리하고 싶을 때가 많다.

이때 등장하는 개념이 바로 제네릭(Generics)이다.

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

제네릭이 왜 필요한가?

 

제네릭이 없으면 보통 object로 다 받아서 처리해야 하는데,
저장은 편하지만 꺼낼 때마다 캐스팅이 필요하다.
또한, 잘못된 타입이 들어가도 컴파일 단계에서 못 잡고 런타임 오류가 날 수 있다.

(C#에서는 값 타입의 경우 박싱/언박싱 비용도 생길 수 있음)

 

 

타입 안전성 향상

제네릭을 사용하면, 컴파일 시점에 자료형이 확정되므로, 잘못된 타입의 객체를 넣거나 꺼내는 것을 방지할 수 있다.

컴파일할 때 타입을 체크해서 에러를 사전에 잡을 수 있다

 

 

코드 재사용성 향상

동일한 로직을 타입만 달리하여 재사용 할 수 있다.

(중복 코드를 줄이고 유지보수를 쉽게 만들 수 있다.)

 

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------


제네릭 코드 예시 (C#, Unity 기준)

 

1) 제네릭 클래스

public class Box<T>
{
    public T Value { get; private set; }

    public Box(T value)
    {
        Value = value;
    }
}
var intBox = new Box<int>(10);
var strBox = new Box<string>("hello");

 

 

2) 제네릭 메서드

public static T First<T>(T[] arr)
{
    return arr[0];
}
int x = First(new int[] { 1, 2, 3 });
string s = First(new string[] { "a", "b" });

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

T는 뭐냐? (타입 매개변수)


T는 특별한 의미가 있는 게 아니라, 관례적으로 쓰는 이름이다.

T = Type(타입)

TKey, TValue = Dictionary에서 흔히 사용
TItem, TResult 등도 자주 사용

 


핵심은 코드를 작성할 때는 T라는 “자리표시자”로 두고
사용할 때 T 자리에 실제 타입을 지정한다는 점이다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------


제네릭 제약(where) 

 

제네릭을 사용할때,

만약 “T가 반드시 어떤 조건을 만족해야 한다”고 제한하고 싶다면

 이때 제약(Where)을 쓴다.

 

 

자주 쓰는 제약 예시 (C#)

// 참조 타입만 허용
where T : class

// 값 타입만 허용
where T : struct

// 매개변수 없는 생성자 필요
where T : new()

// 특정 인터페이스/베이스 타입을 반드시 구현/상속
where T : IComparable

 

 

이걸 쓰면, 제네릭 내부에서 “T가 이런 기능을 제공한다”는 전제를 두고 안전하게 코드를 작성할 수 있다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

제네릭 - 오브젝트 풀링(Object Pooling)

 

오브젝트 풀링은 자주 생성/파괴되는 오브젝트를 ‘만들어두고 재사용’하여 성능을 안정화하는 기법이다.

 

(풀 내의 오브젝트는 파괴되지 않고 메모리에 계속 남아있기 때문에,
CPU 성능 소모를 줄이고 메모리 사용량을 더 늘리는 기법이라고도 할 수 있다.
)

 

 

 

오브젝트 풀링 예시코드

 

1) SimpleObjectPool.cs (풀 매니저)

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private int initialSize = 20;
    [SerializeField] private Transform poolParent;	//오브젝트를 모아놓을 루트 오브젝트

    private readonly Queue<GameObject> pool = new Queue<GameObject>();

    private void Awake()
    {
        if (poolParent == null) poolParent = transform;

        for (int i = 0; i < initialSize; i++)
        {
            var obj = CreateNew();
            ReturnToPool(obj);
        }
    }

    private GameObject CreateNew()
    {
        var obj = Instantiate(prefab, poolParent);
        obj.SetActive(false);
        return obj;
    }

    // 꺼내기
    public GameObject Get(Vector3 position, Quaternion rotation)
    {
        var obj = pool.Count > 0 ? pool.Dequeue() : CreateNew();

        obj.transform.SetPositionAndRotation(position, rotation);
        obj.SetActive(true);
        return obj;
    }

    // 반환
    public void Release(GameObject obj)
    {
        ReturnToPool(obj);
    }

    private void ReturnToPool(GameObject obj)
    {
        obj.SetActive(false);
        obj.transform.SetParent(poolParent);
        pool.Enqueue(obj);
    }
}

 

 

2) PoolUserExample.cs (사용 예: 마우스 클릭 시 생성, 2초 후 반납)

using System.Collections;
using UnityEngine;

public class PoolUserExample : MonoBehaviour
{
    [SerializeField] private SimpleObjectPool pool;
    [SerializeField] private Transform spawnPoint;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            var obj = pool.Get(spawnPoint.position, spawnPoint.rotation);
            StartCoroutine(ReturnAfterSeconds(obj, 2f));
        }
    }

    private IEnumerator ReturnAfterSeconds(GameObject obj, float seconds)
    {
        yield return new WaitForSeconds(seconds);
        pool.Release(obj);
    }
}

 


----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

오늘의 한 줄 요약


'제네릭'은 타입을 매개변수처럼 받아, 한 번 만든 코드를 다양한 타입에 재사용하면서도 타입 안전성을 유지하는 기능이다.

반응형

+ Recent posts