제네릭(Generic)
제네릭(Generic)은 C#에서 타입에 독립적인 코드를 작성할 수 있게 해주는 기능입니다.
제네릭을 사용하면, 같은 코드로 여러 가지 데이터 타입을 처리할 수 있으며, 타입 안정성을 보장할 수 있습니다.
왜 제네릭을 사용할까요?
- 코드 재사용성 : 제네릭을 사용하면 여러 타입에 대해 동일한 동작을 수행하는 코드를 하나만 작성해도 됩니다.
예를 들어, 'List'와 'List' 모두 'List'라는 제네릭 리스트 클래스를 사용하여 구현할 수 있습니다. - 타입 안정성 : 제네릭을 사용하면 특정 타입에 대해 안전하게 코드를 작성할 수 있습니다.
잘못된 타입을 사용하는 경우, 컴파일 시점에 오류를 확인할 수 있습니다.
제네릭의 예시
C#에서 가장 많이 사용되는 제네릭의 예는 'List'입니다.
여기서 'T'는 타입 매개변수로, 리스트가 어떤 타입의 데이터를 담을지 지정할 수 있습니다.
// int 타입의 데이터를 담는 리스트
List<int> intList = new List<int>();
// string 타입의 데이터를 담는 리스트
List<string> stringList = new List<string>();
위 코드에서 'intList' 정수형 데이터를, 'stringList'는 문자열 데이터를 담을 수 있습니다.
두 리스트는 동일한 'List' 클래스를 사용하지만, 저장할 데이터 타입이 다릅니다.
유니티에서의 제네릭 사용
유니티에서는 제네릭을 사용하여 다양한 컴포넌트를 안전하고 효율적으로 처리할 수 있습니다.
대표적인 예로, 'GetComponent()' 메서드가 있습니다.
// GameObject에서 Rigidbody 컴포넌트를 가져옵니다.
Rigidbody rb = gameObject.GetComponent<Rigidbody>();
위 코드에서 'GetComponent()'는 제네릭 메서드로, 'T'자리에 원하는 컴포넌트 타입을 지정할 수 있습니다.
이 방식으로 컴포넌트를 가져올 때, 잘못된 타입을 입력하면 컴파일 시점에 오류를 잡아주기 때문에 안전하게 코드를 작성할 수 있습니다.
제네릭을 사용할 때의 주의사항
- 타입 제한: 제네릭을 사용할 때, 특정 타입만 허용하고 싶다면 'where' 키워드를 사용해 타입을 제한할 수 있습니다.
- 제네릭과 박싱(Boxing): 값 타입(예: int, float)을 제네릭으로 사용할 때는 박싱과 언박싱이 발생할 수 있으므로 성능에 영향을 줄 수 있습니다.
타입 제한(Type Constraints)
제네릭을 사용할 때 특정 타입이나 특정 인터페이스를 구현한 타입만을 허용하도록 제한하는 기능입니다.
이를 통해 제네릭 클래스나 메서드에서 사용 가능한 타입을 명확하게 정의할 수 있습니다.
타입 제한의 필요성
타입 제한을 사용하면, 제네릭 타입으로 전달된 인수가 특정 조건을 충족하도록 강제할 수 있습니다.
예를 들어, 제네릭 타입에 특정 메서드나 속성이 필요할 때, 해당 메서드나 속성을 포함한 인터페이스를 구현한 타입으로 제한할 수 있습니다.
타입 제한 사용 방법
제네릭 타입 정의에서 where 키워드를 사용합니다.
// 특정 클래스 또는 그 파생 클래스만 허용
public class MyClass<T> where T : SomeBaseClass
{
public void DoSomething(T item)
{
// 여기서 T는 SomeBaseClass 또는 그 파생 클래스여야 합니다.
}
}
// 특정 인터페이스를 구현한 타입만 허용
public class MyGenericClass<T> where T : ISomeInterface
{
public void DoSomething(T item)
{
// 여기서 T는 ISomeInterface를 구현한 타입이어야 합니다.
item.SomeMethod(); // ISomeInterface에 정의된 메서드 호출
}
}
// 구조체 (값 타입)만 허용
publi class ValueContainer<T> where T : struct
{
public T Value { get; set; }
}
// 클래스 (참조 타입)만 허용
public class ReferenceContainer<T> where T : class
{
public T Value { get; set ;}
}
// 기본 생성자가 있는 타입만 허용
public class Factory<T> where T : new()
{
public T CreateInstance()
{
// T는 기본 생성자를 가져야 합니다.
return new T();
}
}
유니티와의 연관성
유니티에서 제네릭과 타입 제한을 함께 사용할 수 있는 좋은 예는 커스텀 매니저 클래스입니다.
예를 들어, 특정 컴포넌트를 처리하는 제네릭 메서드를 만들 때, 그 컴포넌트가 반드시 MonoBehaviour를 상속받도록 제한할 수 있습니다.
public class ComponentManager<T> where T : MonoBehaviour
{
private List<T> components = new List<T>();
public void RegisterComponent(T component)
{
components.Add(component);
}
public void UnregisterComponent(T component)
{
components.Remove(component);
}
}
위 코드에서 ComponentManager는 T가 반드시 MonoBehaviour를 상속 받아야 한다는 제한을 두고 있습니다.
이렇게 하면, T는 유니티의 MonoBehaviour의 모든 기능을 사용할 수 있습니다.
이렇게 타입 제한을 활용하면, 제네릭 코드를 더욱 안전하고 효율적으로 작성할 수 있습니다.
C#에서 제네릭을 사용할 때 타입 제한을 적절히 사용하면 코드의 오류를 줄이고, 유지보수성을 높일 수 있습니다.