[Effective C# 개정판 3판] 아이템 25: 타입 매개변수로 인스턴스 필드를 만들 필요가 없다면 제네릭 메서드를 정의하라

참조

소개

  • 제네릭을 사용하다 보면 자칫 무작정 제네릭 클래스를 만드는 습관에 빠지곤 합니다.
  • 하지만 유틸리티 성격의 클래스를 만드는 경우에는 일반 클래스 내에 제네릭 메서드를 작성하는 편이 훨씬 좋습니다.
  • 이번 장에서는 왜 제네릭 메서드를 작성하는게 더 좋은지? 에 대해서 알아 보려고 합니다.

제네릭 메서드를 사용해야 하는 이유

  • 왜 제네릭 메서드로 구현하는게 좋을까요?
  • 우선, 제네릭 클래스를 작성하면 컴파일러 입장에서는 전체 클래스에 대하여 타입 매개변수에 대한 제약 조건을 고려하여 컴파일을 해야 하지만 일반 클래스 내에 제네릭 메서드들을 배치하면 각 메서드별로 제약 조건을 다르게 설정할 수 있습니다.
  • 제네릭 클래스로 작성하여 제약 조건의 범위가 넓어지면 넓어질수록 코드를 수정하기가 점점 더 까다로워 집니다.
  • 때문에, 타입 매개변수로 인스턴스 필드를 만들어야 하는 경우에는 제네릭 클래스를 작성하고 그렇지 않은 경우에는 제네릭 메서드를 작성하는게 좋습니다.

예제

  • 아래와 같이 제네릭 클래스에서 작성된 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

이 글을 공유하기

댓글

Designed by JB FACTORY