참조
- Effective C# <강력한 C# 코드를 구현하는 50가지 전략과 기법, 이펙티브>, 빌 와그너, 김명신, 한빛미디어
- https://docs.microsoft.com/ko-kr/dotnet/csharp/fundamentals/types/generics
소개
- 제네릭을 사용하다 보면 자칫 무작정 제네릭 클래스를 만드는 습관에 빠지곤 합니다.
- 하지만 유틸리티 성격의 클래스를 만드는 경우에는 일반 클래스 내에 제네릭 메서드를 작성하는 편이 훨씬 좋습니다.
- 이번 장에서는
왜 제네릭 메서드를 작성하는게 더 좋은지?
에 대해서 알아 보려고 합니다.
제네릭 메서드를 사용해야 하는 이유
- 왜 제네릭 메서드로 구현하는게 좋을까요?
- 우선, 제네릭 클래스를 작성하면 컴파일러 입장에서는 전체 클래스에 대하여 타입 매개변수에 대한 제약 조건을 고려하여 컴파일을 해야 하지만 일반 클래스 내에 제네릭 메서드들을 배치하면 각 메서드별로 제약 조건을 다르게 설정할 수 있습니다.
- 제네릭 클래스로 작성하여 제약 조건의 범위가 넓어지면 넓어질수록 코드를 수정하기가 점점 더 까다로워 집니다.
- 때문에, 타입 매개변수로 인스턴스 필드를 만들어야 하는 경우에는 제네릭 클래스를 작성하고 그렇지 않은 경우에는 제네릭 메서드를 작성하는게 좋습니다.
예제
- 아래와 같이 제네릭 클래스에서 작성된
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;
}
double d1 = 4;
doubld d2 = 5;
double max = Utile<double>.Max(d1,d2);
string foo = "foo";
string bar = "bar";
string sMax = Utils<string>.MAx(foo, bar);
- 위의 코드는 완벽해 보입니다. 숫자와 문자 모두 비교할 수도 있습니다.
- 위의 코드를 작성한 개발자는 즐거운 마음으로 퇴근했을지 모르지만, 이 클래스를 사용하는 다른 동료들을 그리 즐겁지 않을것 입니다.
- 먼저 이 코드는 메서드 호출 시마다 매번 타입 매개변수를 명시적 으로 지정해야 합니다.
- 또한, 숫자 타입에서는
Math.Min()
,Math.Max()
라는 동일한 기능을 제공하는 함수가 이미 내장되어 있습니다. - 앞의 코드는 이 같은 메서드를 사용할 수 있음에도 불구하고 굳이
Comparer<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.Max(left, right);
// 다른 숫자 타입에 대한 Min 메서드는 생략했다.
}
- 위처럼 코드를 작성하게 되면, 이제
Utils
클래스는 더이상 제네릭 클래스가 아닙니다. - 대신 Min, Max 에 대하여 여러 개의
오버로딩 메서드
를 작성했습니다. - 이처럼 타입을 구체적으로 지정한 메서드는 제네릭 버전보다 효율적으로 동작합니다.
- 그리고 이 메서드를 사용할 때에도 더이상 타입 매개변수를 명시적으로 지정할 필요가 없습니다.
실행 가능한 예제코드
using System;
using System.Collections.Generic;
namespace ConsoleApp5
{
class Program
{
static void Main(string[] args)
{
int number1 = 10;
int number2 = 20;
// 정수형 타입 비교
Console.WriteLine($"Max Value : {Utils<int>.Max(number1, number2)}");
Console.WriteLine($"Min Value : {Utils<int>.Min(number1, number2)}");
string str1 = "abc";
string str2 = "abcdefg";
Console.WriteLine();
// 문자열 타입 비교
Console.WriteLine($"Max Value : {Utils<string>.Max(str1, str2)}");
Console.WriteLine($"Min Value : {Utils<string>.Min(str1, str2)}");
double num1 = 10.2;
double num2 = 22.3;
Console.WriteLine();
Console.WriteLine($"Max Value : {Utils.Max(num1, num2)}");
Console.WriteLine($"Min Value : {Utils.Min(num1, num2)}");
}
}
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;
}
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);
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);
}
}
제네릭 클래스를 사용해야 할 경우
- 클래스 내에 타입 매개변수로 주어진 타입으로 내부 상태를 유지해야 하는 경우(ex. 컬렉션)
- 제네릭 인터페이스를 구현하는 클래스를 만들어야 할 경우
정리
- 타입 매개변수로 인스턴스 필드를 만들어야 하는 경우에는 제네릭 클래스를 작성하고 그렇지 않은 경우에는 제네릭 메서드를 작성합니다.
- 제네릭 메서드로 작성하면 메서드를 호출할 때마다 명시적으로 타입을 지정할 필요가 없습니다.
- 컴파일러가 알아서 최적의 메서드를 찾아 줍니다.
728x90
'C# > Effective C# 책 정리' 카테고리의 다른 글
이 글을 공유하기