전체 코드

using System.ComponentModel;
using System.Numerics;
using System.Threading;
using System.Collections.Generic;

namespace CSharp
{

    //// 타입을 모두 지정해서 클래스를 만든다. -> 너무 비효율적
    //// 
    //class MyIntList
    //{
    //    int[] arr = new int[10];
    //}

    //class MyShortList
    //{
    //    short[] arr = new short[10];
    //}

    //class MyFloatList
    //{
    //    float[] arr = new float[10];
    //}

    //class MyMonsterList
    //{
    //    Monster[] arr = new Monster[10];
    //}

    //// 비효율적인 방법을 타개하기 위해서는 
    //// 첫번째로 Object Type을 사용하면 된다.

    //// 따라서 이런식으로 선언하는것은 좋은 방법이 아님
    //class MyList
    //{
    //    object[] arr = new object[10];
    //}

    // 일반화 방법
    // 일반화 클래스
    // 조건 추가 가능
    class MyList<T> where T : struct // : new(), Monster
    {
        T[] arr = new T[10];

        public T GetItem(int i)
        {
            return arr[i];
        }
    }

    

    class Program
    {
        static void Test<T>(T input)
        {

        }

        static void Main(string[] args)
        {
            // 1. Ojbect
            // 선언을 하고 사용하고 싶을 때는 캐스팅을 해줘야함
            // var라는 타입이랑 같지 않느냐?
            // 개념이 다름
            // var : 뒤에 있는 아이를 보고 타입을 추정하는것

            // object : 타입이 object 타입이 되는것

            // 어떻게 모든 타입을 받을 수 있냐?
            // 모든 데이터 타입은 object를 상속받아서 구현이 되어 있음
            // 그럼 모든 변수를 object를 사용하면 되지 않을 까요? 그렇게 할 수 없음
            // 속도가 너무 느리다는 단점이 있음
            // 왜 속도가 느림?

            // int string 등등은 스택에 바로 사용 되는 변수지만

            // object는 참조타입으로 힙에다가 할당해서 힙에서 가져와서 스택으로 가져옴
            // 따라서 무거운 작업임
            // 데이터를 가져오는 방법을 boxing unboxing이라고함


            //object obj = 3;
            //object obj2 = "Hello World";

            //int num  =(int)obj;
            //string ster = (string)obj2;

            MyList<int> myIntList = new MyList<int>();
            int item = myIntList.GetItem(0);
            MyList<short> myShortList = new MyList<short>();
            MyList<Monster> myMonsterList = new MyList<Monster>();

            Test<int>(3);
            Test<float>(3.0f);

        
        }
    }
}

1️⃣ 왜 제네릭이 필요할까?

✔️ 제네릭 없는 반복 코드 문제

class MyIntList { int[] arr = new int[10]; }
class MyFloatList { float[] arr = new float[10]; }
class MyMonsterList { Monster[] arr = new Monster[10]; }
  • 자료형마다 같은 클래스 구조를 반복해서 만들어야 함
  • 비효율적, 유지보수 어려움

✔️ object를 활용한 일반화 시도 (비추)

class MyList { object[] arr = new object[10]; }
  • 자료형에 관계없이 저장 가능
  • 하지만 박싱/언박싱 발생 → 성능 저하
  • 타입 안전성 보장 어려움 (잘못된 캐스팅 시 런타임 에러)

✔️ 최적해: 제네릭(Generic) 도입

class MyList<T> { T[] arr = new T[10]; }
  • 자료형마다 클래스 반복 작성 필요 없음
  • 박싱/언박싱 없이 컴파일 타임에 타입 결정 (성능 우수)
  • 타입 안정성 보장 (컴파일 시 타입 체크)

2️⃣ 제네릭 클래스 기본 문법

class MyList<T>
{
    T[] arr = new T[10];  // T로 배열 정의
    public T GetItem(int i) { return arr[i]; }
}
요소설명
<T>제네릭 타입 매개변수
T실제 타입 자리
arrT 타입 배열

3️⃣ 제네릭 클래스 사용 예

MyList<int> intList = new MyList<int>();
MyList<Monster> monsterList = new MyList<Monster>();
  • Tint 또는 Monster로 치환되어 컴파일됨
  • 타입별 맞춤 클래스 생성 효과

4️⃣ 제네릭 메서드

static void Test<T>(T input)
{
    Console.WriteLine(input);
}
요소설명
<T>제네릭 타입 매개변수
T input입력 매개변수 타입 일반화

호출 예

Test<int>(3);
Test<string>("Hello");

5️⃣ 박싱/언박싱 문제와 제네릭 비교

✔️ object 사용 시 (비효율)

object obj = 3;  // 박싱 발생
int num = (int)obj;  // 언박싱 발생
작업설명
박싱값 타입을 힙에 감싸 저장
언박싱힙에서 값 타입 꺼내오기
성능느림 (힙 메모리 사용)

✔️ 제네릭 사용 시 (효율적)

MyList<int> list = new MyList<int>();
int item = list.GetItem(0);  // 박싱/언박싱 없음
작업설명
타입 지정컴파일 시 타입 결정
성능빠름 (스택에 직접 저장 가능)
타입 안전성높음 (컴파일 타임 체크)

6️⃣ 제네릭 제약 조건 (where)

제약 조건설명
where T : struct값 타입만 가능 (int, float 등)
where T : class참조 타입만 가능 (string, Monster 등)
where T : new()기본 생성자 필요
where T : 특정클래스특정 클래스 상속 필요
where T : 인터페이스특정 인터페이스 구현 필요

예시

class MyList<T> where T : new() { ... }

7️⃣ 두 개 이상의 타입 매개변수

class MyList2<T, N>
{
    T[] arr = new T[10];
}
  • 필요에 따라 여러 타입 매개변수 사용 가능
  • 대표적 예: Dictionary<TKey, TValue>

profile
李家네_공부방

0개의 댓글