본문 바로가기
카테고리 없음

[C#] 제네릭(Generic)과 활용법

by 오 복 이 2024. 11. 27.

제네릭은 코드의 재사용성유연성을 높여주는 도구로, 타입 안정성까지 제공합니다.  제네릭의 개념과 장점, 제네릭 클래스와 메서드 구현, 제네릭 컬렉션과 제한(Constraints)에 대한 글 입니다.

 

 

1. 제네릭(Generic)이란?

제네릭의 개념

**제네릭(Generic)**은 데이터의 타입을 일반화하여 코드의 재사용성을 높이는 기능입니다. 제네릭을 사용하면 특정 데이터 타입에 의존하지 않는 유연한 클래스, 메서드, 인터페이스를 작성할 수 있습니다.

제네릭을 사용하는 이유

  1. 코드 재사용성: 다양한 데이터 타입에서 동일한 로직을 사용할 수 있습니다.
  2. 타입 안전성: 컴파일 시점에 데이터 타입을 확인하여 오류를 방지합니다.
  3. 성능 최적화: 박싱(Boxing)과 언박싱(Unboxing)을 줄여 성능이 향상됩니다.

제네릭 없이 작성한 코드의 한계

 
class NonGenericList
{
    private object[] items = new object[10];
    private int index = 0;

    public void Add(object item)
    {
        items[index++] = item;
    }

    public object Get(int i)
    {
        return items[i];
    }
}

class Program
{
    static void Main()
    {
        NonGenericList list = new NonGenericList();
        list.Add(10); // 정수 추가
        list.Add("Hello"); // 문자열 추가

        // 데이터 타입을 명시적으로 캐스팅해야 함
        int number = (int)list.Get(0); // 성공
        string text = (string)list.Get(1); // 성공
    }
}
 
 
 

문제점: 타입이 object이므로 데이터가 섞일 가능성이 높고, 매번 캐스팅해야 하며, 잘못된 캐스팅 시 런타임 오류가 발생할 수 있습니다.


2. 제네릭 클래스와 메서드 구현

제네릭 클래스

제네릭 클래스를 사용하면 클래스 내에서 특정 타입에 의존하지 않고 유연하게 데이터를 처리할 수 있습니다.

 
class GenericList<T>
{
    private T[] items = new T[10];
    private int index = 0;

    public void Add(T item)
    {
        items[index++] = item;
    }

    public T Get(int i)
    {
        return items[i];
    }
}

class Program
{
    static void Main()
    {
        // 정수형 리스트
        GenericList<int> intList = new GenericList<int>();
        intList.Add(10);
        int number = intList.Get(0);
        Console.WriteLine($"정수: {number}");

        // 문자열 리스트
        GenericList<string> stringList = new GenericList<string>();
        stringList.Add("Hello");
        string text = stringList.Get(0);
        Console.WriteLine($"문자열: {text}");
    }
}
 
 

💡 <T>는 타입 매개변수로, 클래스가 생성될 때 타입을 결정합니다. 이를 통해 타입 안전성을 유지하면서 재사용 가능한 클래스를 작성할 수 있습니다.

제네릭 메서드

제네릭 메서드는 특정 클래스 내에서 뿐만 아니라, 일반 메서드에서도 유연한 타입을 처리할 수 있도록 합니다.

class Utility
{
    public static void Print<T>(T data)
    {
        Console.WriteLine($"데이터: {data}");
    }
}

class Program
{
    static void Main()
    {
        Utility.Print(123); // 정수 출력
        Utility.Print("Hello"); // 문자열 출력
        Utility.Print(45.67); // 실수 출력
    }
}
 
 
 

💡 제네릭 메서드는 다양한 데이터 타입을 처리하는 로직에서 중복 코드를 줄이는 데 유용합니다.


3. 제네릭 컬렉션 활용

C#의 System.Collections.Generic 네임스페이스에는 제네릭 기반의 강력한 컬렉션 클래스가 포함되어 있습니다.

List<T>

List<T>는 제네릭 기반으로 설계된 가장 많이 사용하는 컬렉션 중 하나입니다.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int>();
        numbers.Add(10);
        numbers.Add(20);
        numbers.Add(30);

        foreach (int number in numbers)
        {
            Console.WriteLine(number);
        }
    }
}
 
 
 

Dictionary<TKey, TValue>

Dictionary<TKey, TValue>는 키-값 쌍으로 데이터를 저장하는 제네릭 컬렉션입니다.

 
class Program
{
    static void Main()
    {
        Dictionary<int, string> students = new Dictionary<int, string>();
        students.Add(1, "홍길동");
        students.Add(2, "김철수");

        foreach (var pair in students)
        {
            Console.WriteLine($"학번: {pair.Key}, 이름: {pair.Value}");
        }
    }
}
 
 

💡 제네릭 컬렉션은 타입 안전성을 유지하면서 성능도 높이기 때문에, 기존 ArrayList나 Hashtable 대신 권장됩니다.


4. 제네릭 제한 (Constraints)

제네릭에서 **제한(Constraints)**을 사용하면 타입 매개변수가 특정 조건을 만족해야만 사용할 수 있도록 제한할 수 있습니다.

기본 사용법

 
class Utility<T> where T : class // T는 참조형 타입이어야 함
{
    public static void Print(T data)
    {
        Console.WriteLine(data.ToString());
    }
}
 
 

다양한 제한 조건

제한설명

where T : class T는 참조형 타입이어야 함
where T : struct T는 값 형식이어야 함
where T : new() T는 기본 생성자가 있어야 함
where T : BaseClass T는 특정 기본 클래스 또는 인터페이스를 상속해야 함

예제: 기본 생성자 제한

 
class Factory<T> where T : new()
{
    public static T Create()
    {
        return new T();
    }
}

class Program
{
    static void Main()
    {
        var obj = Factory<StringBuilder>.Create();
        obj.Append("Hello, Generics!");
        Console.WriteLine(obj.ToString());
    }
}
 
 

5. 제네릭 인터페이스와 Delegate

제네릭 인터페이스

제네릭 인터페이스는 여러 클래스에서 공통적인 기능을 구현하도록 강제할 수 있습니다.

interface IRepository<T>
{
    void Add(T item);
    T Get(int id);
}

class Repository<T> : IRepository<T>
{
    private Dictionary<int, T> data = new Dictionary<int, T>();

    public void Add(T item)
    {
        data[data.Count] = item;
    }

    public T Get(int id)
    {
        return data[id];
    }
}

class Program
{
    static void Main()
    {
        IRepository<string> repo = new Repository<string>();
        repo.Add("Hello");
        Console.WriteLine(repo.Get(0));
    }
}
 
 
 

제네릭 Delegate

델리게이트에도 제네릭을 사용할 수 있습니다.

 
delegate T Transformer<T>(T input);

class Program
{
    static void Main()
    {
        Transformer<int> square = x => x * x;
        Console.WriteLine(square(5)); // 출력: 25
    }
}
 
 
 

 

728x90
반응형