제네릭 메서드(Generic Method)

황현중·2025년 12월 9일

1. 제네릭 메서드란 무엇인가?

한 줄로 정리하면:

“메서드 단위에서 타입을 외부에서 받는 메서드”

제네릭 클래스는 class MyClass<T>처럼 클래스 전체가 타입 매개변수 T를 공유하지만,
제네릭 메서드는 특정 메서드 하나만 타입 매개변수를 가진다.

기본 형태는 이렇게 생겼다:

반환형 메서드이름<T>(매개변수들...)
{
    // T를 타입처럼 사용
}

예를 들어:

static void PrintTwice<T>(T value)
{
    Console.WriteLine(value);
    Console.WriteLine(value);
}

사용 예:

PrintTwice<int>(10);        // T = int
PrintTwice<string>("Hi");   // T = string
PrintTwice(3.14);            // T를 컴파일러가 double로 추론
  • PrintTwice<T> 가 제네릭 메서드
  • <T>형식 매개변수(Type Parameter)
  • PrintTwice<int> 호출 시 Tint
  • 대부분은 PrintTwice(3.14)처럼 쓰면 컴파일러가 T를 자동으로 추론해 준다

2. 제네릭 “클래스”와 제네릭 “메서드” 차이

2-1. 제네릭 클래스

class Box<T>
{
    public T Value;
}
var intBox = new Box<int>();
intBox.Value = 10;

var strBox = new Box<string>();
strBox.Value = "Hello";
  • 클래스 수준에서 T를 받는다.
  • 클래스 안의 필드, 프로퍼티, 메서드 모두가 같은 T를 공유한다.

2-2. 제네릭 메서드

class Util
{
    public static void PrintTwice<T>(T value)
    {
        Console.WriteLine(value);
        Console.WriteLine(value);
    }
}
  • Util 클래스 자체는 제네릭이 아니다.
  • 메서드 PrintTwice<T> 제네릭이다.
  • 각 호출마다 T를 다르게 줄 수 있다.

2-3. 클래스 제네릭 + 메서드 제네릭 같이 쓰는 경우

class Repository<T>
{
    public T GetById(int id)
    {
        // ...
        return default;
    }

    // 메서드만 따로 제네릭
    public U Convert<U>(T source)
    {
        // 여기서 U는 메서드 전용 타입 매개변수
        return default;
    }
}
  • T는 클래스 전체에서 사용하는 타입
  • UConvert 메서드 전용 타입
  • 둘은 서로 독립적이다 (이름만 다를 뿐 둘 다 타입 매개변수)

3. 제네릭 메서드 기본 문법 예제

3-1. 가장 단순한 형태 – Echo

static T Echo<T>(T value)
{
    return value;
}
int a = Echo<int>(10);        // 10
string s = Echo("Hello");      // T를 string으로 자동 추론
  • 어떤 타입이 오든 그대로 되돌려주는 메서드
  • 유틸리티/헬퍼 코드를 만들 때 자주 쓰는 패턴

3-2. 타입 매개변수 여러 개

static Dictionary<TKey, TValue> MakeDic<TKey, TValue>(TKey key, TValue value)
{
    var dic = new Dictionary<TKey, TValue>();
    dic[key] = value;
    return dic;
}
var dic1 = MakeDic<int, string>(1, "One");
var dic2 = MakeDic("ID", 100);   // 타입 추론 → TKey=string, TValue=int

4. 제네릭 메서드 + 제약조건(where)

제네릭 클래스처럼, 제네릭 메서드도 형식 매개변수 제약조건을 걸 수 있다.

4-1. new() 제약 – 기본 생성자 필요

static T CreateAndLog<T>() where T : new()
{
    T obj = new T();
    Console.WriteLine(typeof(T).Name + " 생성됨");
    return obj;
}
class User
{
    public User() { }  // 기본 생성자
}

var user = CreateAndLog<User>();
  • where T : new() 덕분에 new T() 사용 가능
  • T는 반드시 public 기본 생성자를 가져야 한다

4-2. 인터페이스 제약

interface IHasName
{
    string Name { get; set; }
}

static void PrintName<T>(T obj) where T : IHasName
{
    Console.WriteLine(obj.Name);
}
class User : IHasName
{
    public string Name { get; set; }
}

PrintName(new User { Name = "홍길동" });
  • T가 어떤 구체 타입인지 몰라도, IHasName만 구현했다면 Name 속성을 안전하게 사용할 수 있다.

5. 제네릭 메서드, 실무에서 어디에 많이 쓰일까?

사실 이미 엄청 많이 쓰고 있다. 대표적으로:

5-1. 컬렉션/LINQ 확장 메서드

public static T[] ToArray<T>(this IEnumerable<T> source)
public static List<T> ToList<T>(this IEnumerable<T> source)
public static T FirstOrDefault<T>(this IEnumerable<T> source)
public static IEnumerable<TResult> Select<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TResult> selector)
var list = new List<int> { 1, 2, 3 };
int first = list.FirstOrDefault();  // T = int
  • 전부 제네릭 메서드이다.
  • 이 덕분에 하나의 메서드로 모든 타입의 리스트, 컬렉션을 다 다룰 수 있다.

5-2. Task / 비동기 라이브러리

var t = Task.FromResult(10);  // 사실은 Task.FromResult<int>(10)

Task<TResult> 자체도 제네릭 타입이고, 그걸 생성하는 여러 API도 제네릭 메서드로 제공된다.


6. 제네릭 메서드를 쓰는 이유 (장점)

1) 코드 재사용성

타입만 다른 동일한 로직을 여러 버전으로 만들 필요 없이, 한 번만 정의하고 모든 타입에 쓸 수 있다.

2) 타입 안정성

object + 캐스팅으로 처리하는 대신, 제네릭으로 타입을 받으면 컴파일 타임 타입 체크를 받을 수 있다.

3) 박싱/언박싱 감소

값 형식(int, double 등)을 object에 담았다 뺐다 하면 박싱/언박싱이 생기는데, 제네릭을 쓰면 이런 오버헤드가 줄어든다.


7. 대표적인 예: Swap 제네릭 메서드

static void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}
int x = 10, y = 20;
Swap(ref x, ref y);             // T = int

string s1 = "A", s2 = "B";
Swap(ref s1, ref s2);           // T = string
  • int용 Swap, string용 Swap을 따로 만들 필요가 없다.
  • 하나의 Swap<T>로 모든 타입을 처리할 수 있다.

0개의 댓글