한 줄로 정리하면:
“메서드 단위에서 타입을 외부에서 받는 메서드”
제네릭 클래스는 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> 호출 시 T는 intPrintTwice(3.14)처럼 쓰면 컴파일러가 T를 자동으로 추론해 준다class Box<T>
{
public T Value;
}
var intBox = new Box<int>();
intBox.Value = 10;
var strBox = new Box<string>();
strBox.Value = "Hello";
T를 받는다.T를 공유한다.class Util
{
public static void PrintTwice<T>(T value)
{
Console.WriteLine(value);
Console.WriteLine(value);
}
}
Util 클래스 자체는 제네릭이 아니다.PrintTwice<T>만 제네릭이다.class Repository<T>
{
public T GetById(int id)
{
// ...
return default;
}
// 메서드만 따로 제네릭
public U Convert<U>(T source)
{
// 여기서 U는 메서드 전용 타입 매개변수
return default;
}
}
T는 클래스 전체에서 사용하는 타입U는 Convert 메서드 전용 타입static T Echo<T>(T value)
{
return value;
}
int a = Echo<int>(10); // 10
string s = Echo("Hello"); // T를 string으로 자동 추론
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
제네릭 클래스처럼, 제네릭 메서드도 형식 매개변수 제약조건을 걸 수 있다.
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() 사용 가능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 = "홍길동" });
IHasName만 구현했다면 Name 속성을 안전하게 사용할 수 있다.사실 이미 엄청 많이 쓰고 있다. 대표적으로:
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
var t = Task.FromResult(10); // 사실은 Task.FromResult<int>(10)
Task<TResult> 자체도 제네릭 타입이고, 그걸 생성하는 여러 API도 제네릭 메서드로 제공된다.
타입만 다른 동일한 로직을 여러 버전으로 만들 필요 없이, 한 번만 정의하고 모든 타입에 쓸 수 있다.
object + 캐스팅으로 처리하는 대신,
제네릭으로 타입을 받으면 컴파일 타임 타입 체크를 받을 수 있다.
값 형식(int, double 등)을 object에 담았다 뺐다 하면 박싱/언박싱이 생기는데,
제네릭을 쓰면 이런 오버헤드가 줄어든다.
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을 따로 만들 필요가 없다.