제네릭을 사용하다 보면 무심코 제네릭 클래스를 만드는 게 습관이 되어버릴 수도 있다. 하지만 유틸리티 성격의 클래스를 만드는 경우에는 제네릭 클래스를 만드는 것보다 일반 클래스에 제네릭 메서드를 구현하는 것이 더 좋다.
왜 제네릭 메서드로??
왜 제네릭 메서드로 구현하는 것이 좋은가?
우선, 제네릭 클래스를 작성하면 컴파일러 입장에서는 전체 클래스에 대하여 타입 매개변수에 대한 제약 조건을 고려하여 컴파일을 해야 하지만 일반 클래스 내에 제네릭 메서드들을 배치하면 가 메서드 별로 제약 조건을 다르게 설정할 수 있다.
제네릭 클래스로 작성하여 제약 조건의 범위가 넓어지면 넓어질수록 코드를 수정하기가 점점 더 까다로워진다.
정리하자면, 타입 매개변수로 인스턴스 필드를 만들어야 하는 경우에는 제네릭 클래스를 작성하고 그렇지 않은 경우에는 제네릭 메서드를 작성하라.
제네릭 클래스에서 작성된 Min, Max 메서드가있다고 해보자
public static class Utils<T>
{
public static T Max(T left, T right)
=> Comparer<T>.Default.Compare(left, right) < 0 ? right : left;
public static T Min(T left, T right)
=> Comparer<T>.Default.Compare(left, right) < 0 ? left : right;
}
이 코드는 별 문제 없어 보인다. 이 코드로는 숫자를 비교할 수 도 있고 심지어 문자열도 비교할 수 있다.
이 코드를 작성한 작성자는 문제가 없지만 이코드를 사용할 동료는 전혀 즐겁지 않을 것이다.
일단 이 코드는 메서드 호출시마다 매번 타입 매개변수를 명시적으로 지정해줘야 하는 번거로움이 있다.
그리고 숫자 타입에서 Math.Min() ,Math.Max()라는 동일한 기능을 하는 함수가 이미 내장되어 있다. 그렇지만 앞의 코드는 이 같은 메서드르 사용할 수 있음에도 굳이 Comparaer<T>를 꺼내서 사용한다. 결과적으론 동일한 기능을 하게 되겠지만 매번 타입 매개변수가 IComparer<T>를 구현하는지 런타임에확인해야 하기 때문에 성능상 좋지 않다.
개발자라면 타입에 가장 잘 부합하는 최고의 메서드가 자동으로 선택되기를 원할 것이다. 일반 클래스 내에 제네릭 메서드를 구현하면 이러한 기능을 구현하기가 더 용이하다.
public static class Utils
{
public static T Max<T>(T left, T right)
=> Comparer<T>.Default.Compare(left, right) < 0 ? right : left;
public static double Max(double left, double right)
=> Math.Max(left, right);
// 다른 숫자 타입에 대한 Max 메서드는 생략했다.
public static T Min<T>(T left, T right)
=> Comparer<T>.Default.Compare(left, right) < 0 ? left : right;
public static double Min(double left, double right)
=> Math.Min(left, right);
// 다른 숫자 타입에 대한 Min 메서드는 생략했다.
}
이제 Utils 클래스는 제네릭 클래스가 아닌 대신 Min, Max에 대해서 오버로딩 된 메서드들을 작성하엿다. 이와 같이 타입을 구체적으로 지정한 메서드는 제네릭 클래스로 작성할 때보다 효율적으로 동작한다. 그리고 이 메서드들은 일반 메서드를 사용하는 것과 같이 타입 매개변수를 명시적으로 지정할 필요 없다.
제네릭 클래스를 사용해야할 경우
1.클래스 내에 타입 매개변수로 주어진 타입으로 내부 상태를 유지해야하는경우(ex. 컬렉션)
2.제네릭 인터페이스를 구현하는 클래스를 만들어야 할 경우
정리
1. 타입 매개변수로 인스턴스 필드를 만들어야 하는 경우에는 제네릭 클래스를 작성하고 그렇지 않은 경우에는 제네릭 메서드를 작성하라.
2. 제네릭 메서드로 작성하면 메서드를 호출할 때마다 명시적으로 타입을 지정할 필요가 없다.
3. 커파일러가 최적의 메서드를 찾아준다.
참조 - Effective C# <강력한 C# 코드를 구현하는 50가지 전략과 기법, 이펙티브>, 빌 와그너, 김명신, 한빛미디어
'C# > Effective C#' 카테고리의 다른 글
[Effective C#] Item27 인터페이스는 간략히 정의하고 기능의 확장은 확장 메서드를 사용하라. (0) | 2022.10.26 |
---|---|
[Effective C#] Item26 제네릭 인터페이스와 논 제네릭 인터페이스를 함께 구현하라 (0) | 2022.10.26 |
[Effective C#] Item24 베이스 클래스나 인터페이스에 대해서 제네릭을 특화하지 말라 (0) | 2022.10.25 |
[Effective C#] Item 23 타입 매개변수에 대해 메서드 제약 조건을 설정하려면 델리게이트를 활용하라1 (0) | 2022.10.11 |
[Effective C#] Item 22 공변성과 반공변성을 지원하라 (1) | 2022.10.07 |