I'm FanJae.

[20260513] C# ( 제네릭 제약 ) 본문

Unity/Unity 초격차캠프

[20260513] C# ( 제네릭 제약 )

FanJae 2026. 5. 13. 17:02

1. C# 정리 ( 제네릭 제약 )

(1) 정의

- 제네릭(Generic)은 자료형을 나중에 결정할 수 있게 해주는 문법이다.

List <int>
List <string>

- 하지만 제네릭은 어떤 타입이 들어올지 컴파일 시점에 정해지지 않기 때문에, 아무 타입이나 사용하면 문제가 발생할 수 있다.

- 이를 해결하기 위해 사용하는 것이 제네릭 제약 조건(Generic Constraint)이다.

-  제약 조건은 다음과 같이 사용한다.

 

where T : 조건

- 즉, T에는 특정 조건을 만족하는 타입만 사용할 수 있다.


(2) 필요한 이유

- 제네릭은 타입을 일반화하는 문법이지만, 아무 타입이나 허용하면 원하는 기능을 보장할 수 없다. 

- 예를 들어, 공격 기능을 실행해야 하는 상황이라고 가정한다.

class Attacker<T>
{
}

- 이 경우 아래와 같은 타입도 허용하게 된다.

 

Attacker<int>
Attacker<string>

하지만 int나 string은 공격 기능이 존재하지 않는다.

- 따라서 다음과 같이 제한을 걸어야 한다.

 

class Attacker<T> where T :IAttackable
{
}

- 이제 IAttackable 인터페이스를 구현한 타입만 사용할 수 있다.

- 즉, 제네릭에 대하여 제약을 걸어두어 아래와 같은 특징이 생긴다.

        (1) 공격 가능한 객체만 허용된다.

        (2) 잘못된 타입 사용을 방지한다.

        (3) 기능 사용 가능을 보장한다.


(3) Generic 제약 조건 종류

3-1. 참조 타입 제한 (class)

class ReferenceOnly <T> where T :class
{
}

class 제약은 참조 타입만 허용한다.

 

(1) 가능

ReferenceOnly<string> // 참조 타입이므로, 사용이 가능함.

(2) 불가능

ReferenceOnly<int> // 값 타입이므로, 사용이 불가능함.

 

(3) 주의점

- class는 내가 만든 클래스만 의미하는 것이 아니다. 모든 참조 타입을 의미한다.

- 예를들어, string 역시 참조 타입이므로 사용 가능하다.


3-2. 값 타입 제한 (struct)

class ValueOnly<T> where T : struct
{
}

- struct 제약은 값 타입만 허용한다.

 

(1) 가능

ValueOnly<int> // 값 타입이므로, 사용이 가능함.
ValueOnly<bool> // 값 타입이므로, 사용이 가능함.

(2) 불가능

ValueOnly <string> // 참조 타입이므로, 사용이 불가능함

3-3. 기본 생성자 제한 ( new() )

class Factory<T> where T : new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

- new() 제약은 매개변수가 없는 public 기본 생성자가 있는 타입만 허용한다.

- 이 제약이 있어야 제네릭 내부에서 다음 코드 사용이 가능하다.

new T()

제약이 없다면 컴파일 오류가 발생한다.


3-4. 부모 클래스 제한

class Character
{
    public string Name { get; set; }
}

class Warrior : Character { }
class CharacterManager<T> where T : Character
{
    public void PrintName(T character)
    {
        Console.WriteLine(character.Name);
    }
}
Where T : Character

- 여기서 Where T : Character 가 의미하는 바는 아래와 같다.

(1) Character 타입이면 사용이 가능하다.

(2) Character를 상속한 자식 클래스이라면 사용 가능하다. 

- 따라서 CharacterManager 의 PrintName()에서 Name 프로퍼티 사용이 보장된다.


3-5. 인터페이스 제한

interface IAttackable
{
	void Attack();
}
class Monster :IAttackable
{
	public void Attack()
	{
	}
}
class Attacker<T> where T : IAttackable
{
}

- 인터페이스 제약은 특정 기능 구현 여부를 제한할 때 자주 사용한다.


3-6. 여러 개 제약 조건

- 제약 조건은 여러 개를 동시에 사용할 수도 있다.

where T : Gameobject, IDamageable
class Enemy : GameObject, IDamageable // 클래스, 인터페이스 순서
{
    public void TakeDamage()
    {
    }
}

 

(1) 가능

DamageHandler <Enemy>

- Enemy는 GameObject을 상속하고 IDamageable을 구현하기 때문에 이 조건에 만족한다.

 

(2) 불가능

DamageHandler <string>
DamageHandler <GameObject>

- GameObject는 IDamageable을 구현하지 않았기 때문에 사용할 수 없다.


(4) Generic 제약 조건의 장점

① 잘못된 타입 사용 방지

 

- 컴파일 단계에서 잘못된 타입 사용을 막을 수 있다.

where T : IDamageble

- 이 제약이 있다면, 데미지를 처리할 수 없는 객체는 사용할 수 없다.


② 기능 사용 보장

where T : Character

- 이 경우 Character 의 프로퍼티와 메서드를 안전하게 사용할 수 있다.

character.Name

③ 코드 의도가 명확해진다.

where T : MonoBehaviour, IDamageble

MonoBehaviour(Unity 컴포넌트)이면서, 데미지를 받을 수 있는 객체만 허용한다.


(5) Generic 제약 조건 사용 시 주의점

① 클래스 제약은 먼저 작성

where T : GameObject, IDamageable

클래스 제약을 먼저 쓰고, 인터페이스 제약을 뒤에 작성해야 한다.


② new() 제약은 마지막에 작성한다.

where T : Character(), new()

- new() 는 항상 마지막에 작성해야 한다.


③ class 는 참조 타입을 전체 의미한다.

where T : class

- 사용자 정의 클래스만 허용이 아니다.

- string 과 같은 내용도 포함된다.


(6) Unity에서 Generic 제약 조건을 사용하는 이유

- Unity는 Component 기반 구조를 사용한다.

- 즉, 공격 가능, 이동 가능, 데미지 가능, 상호 작용 가능과 같은 기능 중심 설계가 많다.

- 이에 따라서, Generic Constraint를 주로 사용한다.

 

① 클래스 제약은 먼저 작성

where T : Component

- Unity 컴포넌트만 허용한다.

GetComponent<T>() // 대표적인 예시다.

- Component를 가져오는 자료형에 대해서 아래와 같은 작업은 의미가 없다.

GetComponent<int>()

- int는 Unity Component가 아니기 때문이다.


② 기능 기반 제한

where T : IDamageble

데미지를 받을 수 있는 객체만 허용한다.


③ 여러 기능 조합

where T : MonoBehaviour, IDamageable

- Unity Component 이면서, 동시에 데미지를 받을 수 있어야 한다.

- 이처럼, 특정 기능을 가진 객체만 안전하게 처리할 수 있다.


(7) 정리

- Generic Constraint는 제네릭에 사용할 수 있는 타입 범위를 제한하는 기능이다.

1) 잘못된 타입 사용을 방지할 수 있다.
2) 기능 사용 가능을 보장한다.

3) 코드 안정성이 증가한다.

4) Unity 기능 기반 설계를 지원한다.

 

- 특히 Unity 에서는 아래와 같은 형태로 자주 사용된다.

where T : Component
where T : MonoBehaviour
where T : IDamageable

 

 

 

Comments