CS

제네릭

남생이야 2024. 5. 26. 21:34

 

- 제네릭은 특성 형식을 처리하게 구체화할 수 있는 데이터 구조를 만드는 기능을 제공한다. 

- 코드 재사용성과 타입 안전성을 향상시키기 위해 사용한다. 

- 제네릭을 사용하면 클래스, 메서드, 인터페이스 등을 정의할 때 타입을 일반화할 수 있으며, 런타임이 아닌 컴파일 타임에 타입 체크를 할 수 있다. 

 

제네릭의 장점

  • 타입 안전성: 컴파일 타임에 타입 체크를 하기 때문에, 타입 캐스팅과 관련된 오류를 줄일 수 있다.
  • 코드 재사용성: 제네릭 클래스를 한 번 정의하면 다양한 타입에 대해 사용할 수 있어 코드 중복을 줄일 수 있다.
  • 성능 향상: 박싱과 언박싱을 피할 수 있다. 객체에서 캐스팅이 더 이상 필요하지 않아서 형식 검사 작업을 없앨 수 있다. 
  • 작업 속도 향상: 인텔리센스와 같은 도구를 사용해 코드 작성을 도와주는 편집기에서 제네릭 클래스로부터 반환 매개변수를 바로 사용한다. 

 

형식 매개 변수 지침 

제네릭의 매개변수의 일을 이해하기 쉽게 정의해야 한다. 제네릭의 형식 이름용으로는 ' T ' 를 접두어로 사용한다. 

매개 변수의 이름으로 제약 조건 표시를 고려할 수 있다. 

 

제네릭 정의 

 제네릭 클래스 

 제네릭 클래스는 클래스명 뒤에 < > 기호와 제네릭 변수 (T) 를 선언한다. 

T가 나타나는 모든 곳을 대체하는 단일 형식 인수를 제공할 수 있다. 

public class GenericList<T>
{
    private T[] items;
    private int count;

    public GenericList(int size)
    {
        items = new T[size];
        count = 0;
    }

    public void Add(T item)
    {
        if (count < items.Length)
        {
            items[count] = item;
            count++;
        }
    }

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

 

제네릭 인터페이스 선언

public interface IPair<T>
{
    public T First  {get; set;}
    public T Second {get; set;}
}

// 다중 형식 매개변수 지원 인터페이스 
public interface IPair2<TFirst, TSecond> 
{
    public T First  {get; set;}
    public T Second {get; set;}
}

 

 

제네릭 형식에선 형식 매개변수를 얼마든지 선언할 수 있다. . 

 

제네릭 구조체 선언

public struct Pair<T> : IPair<T>
{
	/*
    public Pair(T first)
    {
    	First = first;
        //  구조체에서 생성자는 모든 필드를 초기화 해야한다. 
        // Second = second; 
    }
    
    */


	public Pair(T first)
    {
    	First = first;
        Second = default(T); // default 연산자로 위 문제를 해결할 수 있다.
    }
    
	public Pair(T first, T second)
    {
    	First = first;
        Second = second;
    }
    
    public T First {get; set;}
    public T Second {get; set;}
}

 

 

C#에서 default 연산자는 특정 타입의 기본 값을 생성하는 데 사용된다.
타입이 컴파일 타임에 명확하지 않을 때 사용된다.
기본 값은 타입에 따라 참조 타입은 'null', 값 타입은 정수형은 '0', 불리언은 'false', 문자는 '\0'이다. 

 

 

 

 

튜플 

여러 개의 데이터 타입을 하나로 묶어주는 자료 구조이다. 

 

 

중첩 제네릭 형식 

외부 클래스가 제네릭타입을 가지고 내부 클래스도 제네릭타입을 가진 클래스 형식이다. 

public class OuterClass<T>
{
	// 중첩 클래스 형식 매개변수를 상속한다. 
	// 형식 매개 변수 재사용은 경고를 일으킨다.
    public T OuterValue { get; set; }

    public class InnerClass<U>
    {
        public U InnerValue { get; set; }

        public void DisplayValues(T outerValue)
        {
            Console.WriteLine($"Outer Value: {outerValue}");
            Console.WriteLine($"Inner Value: {InnerValue}");
        }
    }
}

/// 
... 


  		// OuterClass의 인스턴스를 생성
        OuterClass<string> outer = new OuterClass<string>();
        outer.OuterValue = "Hello from OuterClass";

        // InnerClass의 인스턴스를 생성
        OuterClass<string>.InnerClass<int> inner = new OuterClass<string>.InnerClass<int>();
        inner.InnerValue = 123;

////..

 

 

 

제약 조건

 

인터페이스 제약 조건

 제네릭은 형식 매개변수의 제약 조건을 정의하는 기능을 지원한다. 

제약 조건 정의는 where 키워드를 사용할 수 있다. 

// Printable 인터페이스 정의
interface IPrintable
{
    void Print();
}

// 제네릭 클래스 정의, where 키워드를 사용하여 IPrintable 인터페이스를 구현한 클래스만을 받아들임
class Printer<T> where T : IPrintable
{
    private T obj;

    public Printer(T obj)
    {
        this.obj = obj;
    }

    public void PrintObject()
    {
        obj.Print();
    }
}

 

  제네릭 클래스 Printer<T>를 정의하고 where 구문을 사용하여 인터페이스인 IPrintable 인터페이스를 구현한 클래스만을 받아드리도록 사용할 수 있으며 Printer 클래스를사용할 땐 형식 매개변수로 IPrintrable 인터페이스를  구현한 클래스형을 선언해야 한다. 

 

 

더보기

 

using System;

// Printable 인터페이스 정의
interface IPrintable
{
    void Print();
}

// 제네릭 클래스 정의, where 키워드를 사용하여 IPrintable 인터페이스를 구현한 클래스만을 받아들임
class Printer<T> where T : IPrintable
{
    private T obj;

    public Printer(T obj)
    {
        this.obj = obj;
    }

    public void PrintObject()
    {
        obj.Print();
    }
}

// IPrintable 인터페이스를 구현한 클래스
class MyClass : IPrintable
{
    public void Print()
    {
        Console.WriteLine("Printing from MyClass");
    }
}

// IPrintable 인터페이스를 구현하지 않은 클래스
class AnotherClass
{
    // ...
}

class Program
{
    static void Main(string[] args)
    {
        // MyClass를 이용하여 Printer 인스턴스 생성
        Printer<MyClass> printer1 = new Printer<MyClass>(new MyClass());
        printer1.PrintObject(); // 출력: Printing from MyClass

        // AnotherClass는 IPrintable 인터페이스를 구현하지 않으므로 컴파일 오류 발생
        // Printer<AnotherClass> printer2 = new Printer<AnotherClass>(new AnotherClass()); // 컴파일 오류
    }
}

 

 

 

 

 

클래스 형식 제약 조건

형식 매개변수를 특정 클래스 형식으로 사용할 수 있다. 

매개변수로 오는 모든 형식 인수들은 암시적으로 특정 클래스형으로 변환할 수 있어야 한다. 

클래스 형식 제약 조건을 위한 구문은 클래스 형식 제약 조건이 모든 인터페이스 제약 조건 이전에 나와야 한다.

다중 기본 클래스 제약조건은 쓸 수 없다. 

C#은 string이나 Nullable<T>로 형식 매개변수를 제한할 수 없다. (그 형식 매개변수가 가능한게 한 가지기 때문)

public class MyClass<TValue1, TValue2>
    where TValue1 : class // 클래스 형식 제약 조건
    where TValue2 : IComparable // 인터페이스 제약 조건
{
    // 클래스 내용
}

 

// 제네릭 클래스 정의, T는 Animal 클래스 형식이어야 함
class AnimalList<T> where T : Animal
{
    private T[] animals;
    private int currentIndex;

    public AnimalList(int size)
    {
        animals = new T[size];
        currentIndex = 0;
    }

    public void AddAnimal(T animal)
    {
        if (currentIndex < animals.Length)
        {
            animals[currentIndex] = animal;
            currentIndex++;
        }
        else
        {
            Console.WriteLine("Animal list is full.");
        }
    }

    public void PrintAnimals()
    {
        foreach (var animal in animals)
        {
            if (animal != null)
            {
                Console.WriteLine(animal.GetType().Name);
            }
        }
    }
}

// Animal 클래스 정의 (기반 클래스)
class Animal
{
    // Animal의 공통 동작 등을 정의할 수 있음
}

// Dog 클래스 정의 (Animal 클래스를 상속받음)
class Dog : Animal
{
    // Dog에 특화된 동작 등을 정의할 수 있음
}

 

더보기
using System;

// 제네릭 클래스 정의, T는 Animal 클래스 형식이어야 함
class AnimalList<T> where T : Animal
{
    private T[] animals;
    private int currentIndex;

    public AnimalList(int size)
    {
        animals = new T[size];
        currentIndex = 0;
    }

    public void AddAnimal(T animal)
    {
        if (currentIndex < animals.Length)
        {
            animals[currentIndex] = animal;
            currentIndex++;
        }
        else
        {
            Console.WriteLine("Animal list is full.");
        }
    }

    public void PrintAnimals()
    {
        foreach (var animal in animals)
        {
            if (animal != null)
            {
                Console.WriteLine(animal.GetType().Name);
            }
        }
    }
}

// Animal 클래스 정의 (기반 클래스)
class Animal
{
    // Animal의 공통 동작 등을 정의할 수 있음
}

// Dog 클래스 정의 (Animal 클래스를 상속받음)
class Dog : Animal
{
    // Dog에 특화된 동작 등을 정의할 수 있음
}

class Program
{
    static void Main(string[] args)
    {
        // AnimalList 클래스를 이용하여 Dog 객체를 저장하는 리스트 생성
        AnimalList<Dog> dogList = new AnimalList<Dog>(5);
        dogList.AddAnimal(new Dog());
        dogList.AddAnimal(new Dog());
        dogList.PrintAnimals();
    }
}

 

구조체 클래스 제약 조건 

제약 조각에 형식 매개변수를 둘 수 없다. 이는 무한 참조를 막기 위함이다. 

구조체와 클래스 제약 조건은 결합할 수 없다. - 클래스 형식에선 특정 클래스가 필요하다. 이는 참조타입은 참조타입으로 제한된다. 

 

// 이러한 재귀적인 참조는 허용하지 않는다.
public class MyClass<T> where T : OtherClass<T>
{
    // 클래스 내용
}

 

 

다중 제약 조건 

인터페이스는 다중 제약 조건을 얼마든지 지정할 수 있다. 반면에 클래스 형식은 하나 이상 지정할 수 없다. 

' , (콤마) '  로 분리해서 선언한다. 둘 이상의 형식 매개변수가 있다면 저 마다 where 키워드 뒤에 나와야 한다. 

하나의 형식 매개변수 중에서 다중 제약 조건을 지정할 때 AND 관계를 가진다.

public class EntityDictionary<TKey, TValue>
		: Dictionary<TKey, TValue>
        where TKey : ICompare<TKey>, IFormattable
        where TValue : EntityBase
{
		...
}