C# 문법 공부 : Generics

<제네릭이란?>

보통 클래스나 인터페이스, 메서드를 사용할 때, 동일한 기능을 수행하지만, 입력하는 데이터 형식만 틀린 경우 매개변수를 일일히 넣어서 클래스나 인터페이스, 메서드를 만들지 않고, 제네릭 타입(Generic Type)을 사용하여 만들 수 있음.

 

<제네릭 특징>

1.코드 재사용성이 높습니다. 2.성능이 좋습니다.

 

namespace Generics;
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
        //new LinqGeneric().Strat();
        //new Generic1_2().Start();
        new Generic4_2().Start();

    }
}

 

 


Generic1_1

using System;

/*
    받는 변수 타입이 다르지만 같은 기능을 하는 메서드 여러개를 만든 것
    제네릭 사용 x
*/
class Generic1_1
{
    static void ArrayIntPrint(int[] Num)
    {
        for (int Temp = 0; Temp < Num.Length; ++Temp)
        {
            Console.Write("{0} ", Num[Temp]);
        }
        Console.WriteLine();
        //foreach (int Temp in Num)
        //{
        //    Console.WriteLine("{0}", Temp);
        //}
    }

    static void ArraydoublePrint(double[] Num)
    {
        for (int Temp = 0; Temp < Num.Length; ++Temp)
        {
            Console.Write("{0} ", Num[Temp]);
        }
        Console.WriteLine();
    }

    static void ArrayStringPrint(String[] Num)
    {
        for (int Temp = 0; Temp < Num.Length; ++Temp)
        {
            Console.Write("{0} ", Num[Temp]);
        }
        Console.WriteLine();
    }

    void Start()
    {
        int[] Numbers1 = {1,3,5,7,9 };
        ArrayIntPrint(Numbers1);
        Console.WriteLine();
        
        double[] Numbers2 = { 1.1, 3.1, 5.1, 7.1, 9.1 };  // float형은 f를 붙여야하고, 그냥 실수면 double형이다.
        ArraydoublePrint(Numbers2);
        Console.WriteLine();

        string[] Numbers3 = { "일","이","삼","사"};  // 문자열은 ""를 붙여줘야한다.
        ArrayStringPrint(Numbers3);
        Console.WriteLine();

    }
}

 

 

Generic1_2

using System;
/*
    Generic1.cs에서 변수 타입을 제네릭을 사용하여 메소드 1개로 합친 형태
*/

class Generic1_2
{
    static void ArrayPrint<T>(T[] Num){
        for(int i=0; i<Num.Length; ++i)
        {
            Console.Write("{0} ", Num[i]);
        }
        Console.WriteLine();
    }

    public void Start()
    {
        int[] Numbers1 = { 1, 3, 5, 7, 9 };
        double[] Numbers2 = { 1.1, 3.1, 5.1, 7.1, 9.1 };  // float형은 f를 붙여야하고, 그냥 실수면 double형이다.
        string[] Numbers3 = { "일", "이", "삼", "사" };  // 문자열은 ""를 붙여줘야한다.

        ArrayPrint<int>(Numbers1);      //제네릭 형태 메소드 부르기
        ArrayPrint<double>(Numbers2);   //메소드명<변수타입>(ABC); 변수타입과 넘길 변수의 형식을 맞춰 작성해줌
        ArrayPrint<string>(Numbers3);
    }
}

 

 


Generic2_1

using System;
/*
    인자가 2개인 일반 메서드
    int 배열을 2개씩 선언해줘서 int1에 있는 값을 int2에 대입하는 예제
*/

class Generic2_1
{
     static void ArrayPrint<T>(T[] Num)
    {
        for (int Temp = 0; Temp < Num.Length; ++Temp)
        {
         Console.Write("{0}  ", Num[Temp]);
        }
        Console.WriteLine();
    }

    static void ArrayIntCopy(int[] Dst, int[] Src)
    {
        for(int Temp = 0; Temp < Dst.Length; ++Temp)
        {
            Dst[Temp] = Src[Temp];
        }
    }

    static void ArraydoubleCopy(double[] Dst, double[] Src)
    {
        for (int Temp = 0; Temp < Dst.Length; ++Temp)
        {
            Dst[Temp] = Src[Temp];
        }
    }
    
    public void Start()
    {
        int[] ArrayInt1 = {2, 4, 6, 8}; // 힙영역에 만들어서 값을 넣은거임
        int[] ArrayInt2 = new int[4];   // 힙영역에 만들기만했고, 0으로 채워져있는거임.
        ArrayIntCopy(ArrayInt2, ArrayInt1); //ArrayInt2 = ArrayInt1;와 같은 기능을 하게 만든 메서드
        ArrayPrint<int>(ArrayInt2);

        double[] Arraydouble1 = { 2.1, 4.1, 6.1, 8.1 };
        double[] Arraydouble2 = new double[4]; 
        ArraydoubleCopy(Arraydouble2, Arraydouble1);
        ArrayPrint<double>(Arraydouble2);
    }
}

 

Generic2_2

using System;
/*
    인자가 2개인 제네릭
    int 배열을 2개씩 선언해줘서 int1에 있는 값을 int2에 대입하는 예제
    Generic2_1.cs 코드의 중복 부분을 제네릭을 사용하여 합침
*/

class Generic2_2
{
     static void ArrayPrint<T>(T[] Num)
    {
        for (int Temp = 0; Temp < Num.Length; ++Temp)
        {
         Console.Write("{0}  ", Num[Temp]);
        }
        Console.WriteLine();
    }

    static void ArrayCopy<T>(T[] Dst, T[] Src)
    {
        for(int Temp = 0; Temp < Dst.Length; ++Temp)
        {
            Dst[Temp] = Src[Temp];
        }
    }

    public void Start()
    {
        int[] ArrayInt1 = {2, 4, 6, 8}; // 힙영역에 만들어서 값을 넣은거임
        int[] ArrayInt2 = new int[4];   // 힙영역에 만들기만했고, 0으로 채워져있는거임.
        ArrayCopy(ArrayInt2, ArrayInt1); //ArrayInt2 = ArrayInt1;와 같은 기능을 하게 만든 메서드
        ArrayPrint<int>(ArrayInt2);

        double[] Arraydouble1 = { 2.1, 4.1, 6.1, 8.1 };
        double[] Arraydouble2 = new double[4]; 
        ArrayCopy(Arraydouble2, Arraydouble1);
        ArrayPrint<double>(Arraydouble2);
    }
}

 


Generic3

using System;

/*
    제네릭 : 넘기는 다른 타입의 인자값 2개를 한번에 선언
*/
class Program
{
    static void TwoPrint<T1, T2>(T1 Arg1, T2 Arg2)
    {
        Console.WriteLine(Arg1);
        Console.WriteLine(Arg2);
    }
    public void Start()
    {
        TwoPrint(3, 2.1);
        TwoPrint("헬로", 4.0f);
    }
}

 


Generic4_1

using System;

/*
    일반 클래스를 사용한 예제
*/
class Generic4_1
{
    public void Start()
    {
        FACT obj = new FACT();
        obj.Value = 100;
        obj.Print();
    }
}

public class FACT
{
    public int Value;
    public void Print()
    {
        Console.WriteLine("FACT Value = {0}", Value);
    }
}

 

Generic4_2

using System;

/*
    Generic4_1.cs 코드를 제네릭 클래스 사용 형태로 변경한 예제
*/
class Generic4_2
{
    public void Start()
    {
        // 제레릭 클래스를 호출할때는 아래와 같이 선언
        FACT<int> obj = new FACT<int>(); //클래스명<type> abc = new 클래스명<type>(); 
        obj.Value = 100;
        obj.Print();
    }
}

public class FACT<T> // class이름 뒤에 '<T>'를 붙여 제네릭 클래스를 나타냄
{
    public int Value;   //인자가 1개일때는 변수 타입을 바로 지정하여 public int Value;형태로 작성하여도 T가 바로 value로 들어감
                        //인자가 2개 이상일때는 public T1 Value; 형태로 작성해야 함 
    public void Print()
    {
        Console.WriteLine("FACT Value = {0}", Value);
    }
}

 

 


Generic5

using System;

/*
    제네릭 클래스 인자 2개 선언
*/
class Generic5
{
    public void Start()
    {
        FACT<int,string> obj = new FACT<int, string>();
        obj.Value1 = 100;
        obj.Value2 = "제네릭 인자2개 연습";
        obj.Print();
    }
}

public class FACT<T1,T2> // class이름 뒤에 '<T>'를 붙여 제네릭 클래스를 나타냄
{
    //받은 제네릭 인자 타입을 변수로 선언
    public T1 Value1; 
    public T2 Value2;

    public void Print()
    {
        Console.WriteLine("FACT Value = {0}, {1}", Value1, Value2);
    }
}

 


LinqGeneric

using System;
using System.Collections.Generic;
using System.Linq;

public class LinqGeneric
{
    /*
        Linq 제네릭 : 클래스형
    */
    public class Customer{
        
        public string LastName { get; set; }
        public string FirstName { get; set; }
        public string City { get; set; }
        public List<int> Scores;
    }
    
    public static List<Customer> customers = new List<Customer>
    {
        new Customer {LastName="Kim", FirstName="AA", City="London"},
        new Customer {LastName="Sun", FirstName="BB", City="London"},
        new Customer {LastName="Park", FirstName="CC", City="Seoul"},
        new Customer {LastName="Yee", FirstName="DD", City="Pusan"}
    };

    public void Strat(){

        IEnumerable<Customer> customerQuery =
        from cust in customers
        where cust.City == "London"
        select cust;

        foreach (Customer customer in customerQuery)
        {
            Console.WriteLine(customer.LastName + ", " + customer.FirstName);
        }

        var customerQuery2 =
            from cust in customers
            where cust.City == "London"
            select cust;

        foreach(var customer in customerQuery2)
        {
            Console.WriteLine(customer.LastName + ", " + customer.FirstName);
        }
    }
}

 

 

 


GenericExample

/*
    <제네릭이란?>
    보통 클래스나 인터페이스, 메서드를 사용할 때, 동일한 기능을 수행하지만, 입력하는 데이터 형식만 틀린 경우
    매개변수를 일일히 넣어서 클래스나 인터페이스, 메서드를 만들지 않고, 제네릭 타입(Generic Type)을 사용하여 만들 수 있음.

    <제네릭 특징>
    1.코드 재사용성이 높습니다.
    2.성능이 좋습니다.
*/

//동일 기능을 하지만 변수 타입만 다른 메서드 2개를 제네릭을 사용하여 1개의 메서드로 변경할 수 있음
class GenericExample{
    //동일 한 기능을 하는 메서드 Swap1, Swap2
    void Swap1(ref int x, ref int y)
    {
        var temp = y;
        y = x;
        x = temp;
    }
    void Swap2(ref float x, ref float y)
    {
        var temp = y;
        y = x;
        x = temp;
    }

    // Swap1,2를 Swap3<T> 제네릭 메서드로 합쳐서 만들 수 있음
    void Swap3<T>(ref T x, ref T y)
    {
        var temp = y;
        y = x;
        x = temp;
    }

}


/*
    <제내릭 사용방법>
    멤버변수의 타입을 미리 결정하지 않고, 사용할 때 제넥릭 클래스를 사용합니다.
    public class 클래스-이름<T> 
    { public T ... }
*/
public class GenericsClassExample 
{ 
    public class GenericsClass<T>
    {
        private T m_Value; 

        public GenericsClass(T value) 
        { 
            m_Value = value; 
        } 

        public void Method1() 
        { 
            Console.WriteLine(m_Value); 
        } 
    } 

    void Start() 
    { 
        GenericsClass<int> m_GenericsClass1 = new GenericsClass<int>(5); 
        m_GenericsClass1.Method1(); // 출력 : 5 
        GenericsClass<float> m_GenericsClass2 = new GenericsClass<float>(5.1f); 
        m_GenericsClass2.Method1(); // 출력 : 5.1 
    } 
}

/*
    <제네릭 클래스>
    C#는 기본적으로 제공하는 제네릭 클래스가 많습니다.
    대표적인 것이 List<T>, Dictionary<T>, LinkedList<T> 등이 있습니다.

    <제네릭 타입 제약 (Type Constraint)>
    제네릭 타입을 선언할 때 제약 조건을 걸 수 있습니다.
    사용방법은 제네릭 선언 후 where T : 제약조건 과 같은 식으로 where 뒤에 제약 조건을 붙이면 됩니다.
        <타입제약>
            new 제약 조건1 : class Class1<T> where T : new() // T는 디폴트 생성자를 가져야 함
            new 타입 제약2 : class Class1<T> where T : IComparable, new() // T는 디폴트 생성자를 가져야 함
        <형식에 대한 조건>
            class Class0<T> where T : new()         // T는 매개 변수가 없는 public 생성자가 있어야 합니다
            class Class1<T> where T : struct        // T는 Value 타입임 : null을 허용하지 않는 값 형식
            class Class2<T> where T : class         // T는 참조 타입임
            class Class4<T> where T : Class3        // T는 Class3의 파생클래스어야 함
            class Class4<T> where T : IComparable   // T는 IComparable 인터페이스를 가져야 함.
            class Class5<T, U>                      // 복수 타입의 매개변수 제약
                where T : class
                where U : struct
            { }
     <제네릭 메서드>
        T 메소드-이름<T>(T arg)
        {
            T temp = arg;
            //...
            return temp;
        }
*/

public class GenericExample2 
{ 
    void Start() 
    { 
        int iX = 1, iY = 2; 
        Swap(ref iX, ref iY); 
        Console.WriteLine($"x = {iX} y = {iY}"); // 출력 : x = 2 y = 1  

        string sX = "ab", sY = "cd"; 
        Swap(ref sX, ref sY); 
        Console.WriteLine($"x = {sX} y = {sY}"); // 출력 : x = cd y = ab  
    } 

    void Swap<T>(ref T x, ref T y) 
    { 
        var temp = y; 
        y = x; 
        x = temp; 
    } 
}

//ref를 사용하면 Add(ref abc); 형태만 작성하더라도 abc의 변수값이 Add 함수 내에서 변경된 값으로 저장됨.


/*
    <제내릭 배열>
    
*/
class GenericExample3
{

    void Start()
    {
        int[] arr = { 0, 1, 2, 3, 4 };
        List<int> list = new List<int>();

        for (int x = 5; x < 10; x++)
        {
            list.Add(x);
        }

        ProcessItems<int>(arr);
        ProcessItems<int>(list);
    }

    static void ProcessItems<T>(IList<T> coll)
    {
        // IsReadOnly는 배열에 대해 True를 반환하고 목록에 대해 False를 반환합니다.
        //coll.IsReadOnly : ICollection<T>가 읽기 전용인지 여부를 나타내는 값을 가져옵니다.
        Console.WriteLine("IsReadOnly returns {0} for this collection.", coll.IsReadOnly);

        // 다음 명령문은 배열에 대해 런타임 예외를 발생시키지만 목록에 대해서는 그렇지 않습니다.
        //coll.RemoveAt(4);
        foreach (T item in coll)
        {
            System.Console.Write(item.ToString() + " ");
        }
        System.Console.WriteLine();
    }
}

/*
    <제네릭 대리자>
    delegate EventHandler
*/
class GenericExample4
{
    delegate void StackEventHandler<T, U>(T sender, U eventArgs);

    class Stack<T>
    {
        public class StackEventArgs : System.EventArgs { }
        public event StackEventHandler<Stack<T>, StackEventArgs> StackEvent;

        protected virtual void OnStackChanged(StackEventArgs a)
        {
            StackEvent(this, a);
        }
    }

    class SampleClass
    {
        public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
    }

    public static void Test()
    {
        Stack<double> s = new Stack<double>();
        SampleClass o = new SampleClass();
        s.StackEvent += o.HandleStackChange;
    }
}

'C#' 카테고리의 다른 글

C# 문법 공부 : HttpWebRe  (0) 2024.04.29
C# 문법 공부 : Gif Image  (0) 2024.04.29
C# 문법 공부 : Files  (0) 2024.04.29
C# 문법 공부 : DelegateSample  (0) 2024.04.29
C# 문법 공부 : Network  (0) 2024.04.29