Computer >> 컴퓨터 >  >> 프로그래밍 >> C#

C# 제네릭(Generic) 완벽 가이드: 작동 원리부터 활용법까지

제네릭(Generics)은 C# 2.0에서 도입된 기능으로, 이 언어에서 가장 중요한 개념 중 하나로 꼽힙니다. 제네릭을 사용하면 컴파일 시점에 타입 안전성을 보장하면서 재사용 가능하고 고성능인 코드를 작성할 수 있습니다. 즉, 코드를 작성하는 시점에 타입을 미리 알지 못해도 나중에 해당 타입을 유연하게 사용할 수 있습니다.

제네릭은 .NET 전반에서 폭넓게 활용됩니다. 컬렉션, 델리게이트, 비동기 코드 등이 대표적인 예입니다. 제네릭을 사용하면 컬렉션의 크기를 미리 알 필요가 없으며, 직접 정의한 사용자 지정 데이터 타입을 포함한 어떤 요소 타입과도 함께 사용할 수 있습니다. C#은 제네릭 형식(클래스, 인터페이스 등)과 제네릭 메서드를 모두 지원합니다.

제네릭에는 타입 매개변수(type parameter)타입 인수(type argument)라는 개념이 있습니다. 이는 메서드가 매개변수를 선언하고, 호출 시 인수를 전달하는 방식과 매우 유사합니다.

제네릭 형식(Generic Types)

제네릭 형식을 선언하는 문법은 형식 이름 뒤에 꺾쇠괄호(<>)로 타입 매개변수를 표기하는 것입니다. 아래 예제에서 Locator<T>는 제네릭 클래스입니다.

public class Locator<T>
{

}

Locator<T>의 인스턴스를 생성하려면 new 키워드 뒤에 클래스 이름을 작성하되, T 대신 실제로 전달하고자 하는 타입을 지정합니다. 다음 예제는 string 타입을 인수로 전달합니다.

var stringLocator = new Locator<string>();

아래 예제처럼 클래스의 메서드에서도 타입 매개변수(T)를 사용할 수 있습니다.

public class Locator<T>{
    public IList<T> Items { get; set; }

    public T Locate(int index){
        return Items[index];
    }
}
var stringLocator = new Locator<string>();
string item = stringLocator.Locate(2);

제네릭의 또 다른 장점은 편집기가 제공하는 IntelliSense입니다. Visual Studio나 VS Code에서 stringLocator.Locate(4)를 입력한 후 메서드 이름 위에 마우스를 올리면, 반환 타입이 추상적인 T가 아니라 string임을 바로 확인할 수 있습니다. 만약 결과를 string이 아닌 다른 타입에 할당하려고 하면 컴파일러가 오류를 발생시킵니다.

// 오류: 'string' 형식을 'int'로 암시적으로 변환할 수 없습니다 [c-sharp]csharp(CS0029)
int item = stringLocator.Locate(2);

제네릭 형식은 제네릭 기본 형식이나 제네릭 인터페이스를 상속·구현할 때 자신의 타입 매개변수를 타입 인수로 그대로 사용할 수 있습니다. 예를 들어 LinkedList<T> 형식은 IEnumerable<T>를 비롯한 여러 제네릭 인터페이스를 구현합니다.

public class LinkedList<T> : IEnumerable<T>

제네릭 메서드(Generic Methods)

제네릭 메서드란 메서드 내부에서 사용할 수 있고, 매개변수 및 반환 타입으로도 활용할 수 있는 타입 매개변수를 선언한 메서드를 말합니다. 아래 예제에서 Swap<T>는 T 타입의 두 매개변수를 받아 T의 인스턴스를 반환하는 제네릭 메서드입니다.

public class Swapper{
    public T Swap<T>(T first, T second){
        T temp = first;
        first = second;
        second = temp;
        return temp;
    }
}

제네릭 형식과 마찬가지로, 제네릭 메서드를 호출하면 강력한 형식(strongly typed)의 변수가 반환됩니다.

var swapper = new Swapper();
int result = swapper.Swap<int>(5, 3);

타입 매개변수는 여러 개 사용할 수도 있습니다. System.Collections.Generic 네임스페이스의 Dictionary 클래스는 키와 값에 각각 하나씩, 총 두 개의 타입 매개변수를 가집니다.

public class Dictionary<TKey, TValue>

마지막으로, 무엇이 제네릭이 될 수 있는지 아는 것이 중요합니다. 형식의 경우 열거형(enum)을 제외한 모든 것이 제네릭이 될 수 있으며, 여기에는 다음이 포함됩니다.

  • 클래스(class)
  • 구조체(struct)
  • 인터페이스(interface)
  • 델리게이트(delegate)

반면 형식 멤버 중에서는 메서드와 중첩 형식만 제네릭이 될 수 있습니다. 다음 멤버들은 제네릭으로 선언할 수 없습니다.

  • 필드(Field)
  • 속성(Property)
  • 인덱서(Indexer)
  • 생성자(Constructor)
  • 이벤트(Event)
  • 종료자(Finalizer)

종합 예제

using System;
using System.Collections.Generic;
class Program{
    static void Main(){
        var stringLocator = new Locator<string>(){
            Items = new string[] { "JavaScript", "CSharp", "Golang" }
        };
        string item = stringLocator.Locate(1);
        Console.WriteLine(item); // CSharp
        var swapper = new Swapper();
        int a = 5, b = 3;
        int result = swapper.Swap<int>(ref a, ref b);
        Console.WriteLine($"a = {a}, b = {b}");
    }
}
public class Locator<T>{
    public IList<T> Items { get; set; }
    public T Locate(int index){
        return Items[index];
    }
}
public class Swapper{
    public T Swap<T>(ref T first, ref T second){
        T temp = first;
        first = second;
        second = temp;
        return temp;
    }
}

실행 결과

CSharp
a = 3, b = 5