달력

2

« 2025/2 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
2016. 8. 2. 02:33

제네릭 요약 프로그래밍/C#2016. 8. 2. 02:33

https://msdn.microsoft.com/ko-kr/library/0x6a29h6.aspx


제네릭 소개


예제

1. 중첩 Node 클래스에서도 T를 사용할 수 있습니다.

2. GenericList<int>와 같이 GenericList<T>를 구체적 형식을 사용하여 인스턴스화하면 모든 T가 int로 대체됩니다.


// type parameter T in angle brackets
public class GenericList<T> 
{
    // The nested class is also generic on T.
    private class Node
    {
        // T used in non-generic constructor.
        public Node(T t)
        {
            next = null;
            data = t;
        }

        private Node next;
        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        // T as private member data type.
        private T data;

        // T as return type of property.
        public T Data  
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    // constructor
    public GenericList() 
    {
        head = null;
    }

    // T as method parameter type:
    public void AddHead(T t) 
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }
}


class TestGenericList
{
    static void Main()
    {
        // int is the type argument
        GenericList<int> list = new GenericList<int>();

        for (int x = 0; x < 10; x++)
        {
            list.AddHead(x);
        }

        foreach (int i in list)
        {
            System.Console.Write(i + " ");
        }
        System.Console.WriteLine("\nDone");
    }
}



제네릭의 장점


ArrayList 예제

// The .NET Framework 1.1 way to create a list:
System.Collections.ArrayList list1 = new System.Collections.ArrayList();
list1.Add(3);
list1.Add(105);

System.Collections.ArrayList list2 = new System.Collections.ArrayList();
list2.Add("It is raining in Redmond.");
list2.Add("It is snowing in the mountains.");


1. ArrayList에 추가되는 모든 참조나 값 형식은 Object에 암시적으로 업캐스팅됩니다.

2. 항목이 값 형식이면 이를 목록에 추가할 때 boxing해야 하고 이를 검색할 때 unboxing해야 합니다.

3. 캐스팅이나 boxing 및 unboxing 작업은 모두 성능을 저하시킵니다.

4. 큰 컬렉션을 반복해야 하는 시나리오에서는 boxing과 unboxing의 영향을 결코 무시할 수 없습니다.


System.Collections.ArrayList list = new System.Collections.ArrayList();
// Add an integer to the list.
list.Add(3);
// Add a string to the list. This will compile, but may cause an error later.
list.Add("It is raining in Redmond.");

int t = 0;
// This causes an InvalidCastException to be returned.
foreach (int x in list)
{
    t += x;
}


List<T> 컬렉션 예제

// The .NET Framework 2.0 way to create a list
List<int> list1 = new List<int>();

// No boxing, no casting:
list1.Add(3);

// Compile-time error:
// list1.Add("It is raining in Redmond.");


1. 클라이언트 코드에서 ArrayList와 비교할 때 List<T>을 사용하여 유일하게 추가된 구문은 선언과 인스턴스화의 형식 인수입니다.

2. 이와 같이 코딩이 약간 더 복잡해진 대신 ArrayList보다 안전하면서도 속도가 훨씬 빠른 목록을 만들 수 있습니다.

3. 이러한 차이는 목록 항목이 값 형식인 경우에 특히 잘 드러납니다.



제네릭 형식 매개 변수


1. 제네릭 형식 또는 메서드 정의에서 형식 매개 변수는 클라이언트가 제네릭 형식의 변수를 인스턴스화할 때 지정하는 특정 형식에 대한 자리 표시자입니다.

2. 제네릭 소개(C# 프로그래밍 가이드)에 나열된 GenericList<T> 등의 제네릭 클래스는 실제로 형식이 아니고 형식에 대한 청사진과 같으므로 있는 그대로 사용할 수는 없습니다.

3. 클라이언트 코드에서 GenericList<T>를 사용하려면 꺾쇠괄호 내에 형식 매개 변수를 지정하는 방법으로 생성된 형식을 선언하고 인스턴스화해야 합니다.

4. 이 특정 클래스에 대한 형식 매개 변수의 형식은 컴파일러에서 인식하는 모든 형식이 될 수 있습니다.

5. 만들 수 있는 생성된 형식 인스턴스의 수에는 제한이 없고, 각 인스턴스에서는 다음과 같이 서로 다른 형식 매개 변수를 사용할 수 있습니다.


GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();


6. GenericList<T>의 각 인스턴스에서 클래스에 있는 모든 T는 런타임에 형식 매개 변수로 대체됩니다.

7. 이러한 대체를 통해 단일 클래스 정의를 사용하여 세 개의 형식 안전적이고 효율적인 개체를 개별적으로 만들 수 있습니다.

8. CLR에서 이러한 대체를 수행하는 방식에 대한 자세한 내용은 런타임의 제네릭(C# 프로그래밍 가이드)을 참조하십시오.


형식 매개 변수 명명 지침

1. 필수적 단일 문자 이름으로도 자체 설명이 가능하여 설명적인 이름을 굳이 사용할 필요가 없는 경우가 아니면 제네릭 형식 매개 변수 이름을 설명적인 이름으로 지정하십시오.


public interface ISessionChannel<TSession> { /*...*/ }
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }


2. 선택적 단일 문자 형식 매개 변수를 사용하는 형식에는 형식 매개 변수 이름으로 T를 사용하십시오.


public int IComparer<T>() { return 0; }
public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }


3. 필수적 설명적인 형식 매개 변수 이름 앞에 “T”를 붙이십시오.


public interface ISessionChannel<TSession>
{
    TSession Session { get; }
}


4. 선택적 매개 변수 이름 안에서 형식 매개 변수에 적용되는 제약 조건을 나타내십시오. 예를 들어 ISession으로 제한되는 매개 변수의 이름은 TSession이 될 수 있습니다.




형식 매개 변수에 대한 제약 조건


1. 제네릭 클래스를 정의하는 경우 클래스를 인스턴스화할 때 클라이언트 코드에서 형식 인수에 사용할 수 있는 형식의 종류에 제약 조건을 적용할 수 있습니다.

2. 클라이언트 코드가 제약 조건에서 허용하지 않는 형식을 사용하여 클래스를 인스턴스화하려고 하면 컴파일 타임 오류가 발생합니다.

3. 이러한 제한을 제약 조건(constraint)이라고 합니다.

4. 제약 조건은 컨텍스트 키워드 where를 사용하여 지정합니다. 다음 표에서는 여섯 가지의 형식 제약 조건을 보여 줍니다.


제약 조건

설명

where T: struct

형식 인수가 값 형식이어야 합니다. Nullable를 제외한 임의의 값 형식을 지정할 수 있습니다. 자세한 내용은 Nullable 형식 사용(C# 프로그래밍 가이드)를 참조하십시오.

where T : class

형식 인수가 참조 형식이어야 합니다. 이는 모든 클래스, 인터페이스, 대리자 또는 배열 형식에도 적용됩니다.

where T : new()

형식 인수가 매개 변수 없는 공용 생성자를 가지고 있어야 합니다. 다른 제약 조건과 함께 사용하는 경우 new() 제약 조건은 마지막에 지정해야 합니다.

where T : <기본 클래스 이름>

형식 인수가 지정된 기본 클래스이거나 지정된 기본 클래스에서 파생되어야 합니다.

where T : <인터페이스 이름>

형식 인수가 지정된 인터페이스이거나 지정된 인터페이스를 구현해야 합니다. 여러 인터페이스 제약 조건을 지정할 수 있습니다. 제한하는 인터페이스는 제네릭이 될 수도 있습니다.

where T : U

T에 대해 지정한 형식 인수가 U에 대해 지정한 인수이거나 이 인수에서 파생되어야 합니다.


제약 조건 사용 이유

1. 제네릭 목록의 항목을 검사하여 그 유효성 여부를 확인하거나 이 항목을 다른 항목과 비교하려는 경우, 컴파일러는 클라이언트 코드에서 지정할 수 있는 모든 형식 인수에 대해 호출해야 할 메서드나 연산자가 지원되는지 확인해야 합니다.

2. 하나 이상의 제약 조건을 제네릭 클래스 정의에 적용하면 이러한 확인이 가능합니다.

3. 예를 들어, 기본 클래스 제약 조건에서는 이 형식의 개체와 이 형식에서 파생된 개체만이 형식 인수로 사용될 수 있음을 컴파일러에 알립니다.

4. 컴파일러에서 이를 확인하면 사용자는 제네릭 클래스에서 해당 형식의 메서드를 호출할 수 있습니다.

5. 제약 조건은 컨텍스트 키워드 where를 사용하여 적용됩니다.

6. 다음 코드 예제에서는 기본 클래스 제약 조건을 적용하여 제네릭 소개(C# 프로그래밍 가이드)의 GenericList<T> 클래스에 추가할 수 있는 기능을 보여 줍니다.


public class Employee
{
    private string name;
    private int id;

    public Employee(string s, int i)
    {
        name = s;
        id = i;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int ID
    {
        get { return id; }
        set { id = value; }
    }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        private Node next;
        private T data;

        public Node(T t)
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    public GenericList() //constructor
    {
        head = null;
    }

    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T FindFirstOccurrence(string s)
    {
        Node current = head;
        T t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}


7. 제약 조건을 사용하면 형식 T의 모든 항목이 항상 Employee 개체이거나 Employee에서 상속된 개체이므로 제네릭 클래스에서 Employee.Name 속성을 사용할 수 있습니다.

8. 여러 제약 조건을 동일한 형식 매개 변수에 적용할 수 있고 제약 조건 자체가 제네릭 형식일 수도 있습니다. 예를 들면 다음과 같습니다.


class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}


9. 형식 매개 변수를 제한하면, 허용되는 작업의 수 및 제한하는 형식과 해당 상속 계층 구조의 모든 형식에서 지원하는 메서드에 대해 허용되는 호출의 수를 늘릴 수 있습니다.

10. 따라서 제네릭 클래스나 메서드를 디자인할 때 System.Object에서 지원하지 않는 메서드를 호출하거나 제네릭 멤버에 대해 단순한 할당 이상의 작업을 수행하려는 경우에는 형식 매개 변수에 제약 조건을 적용해야 합니다.

11. where T : class 제약 조건을 적용할 때는 형식 매개 변수에 대해 == 및 != 연산자를 사용하지 않아야 합니다.

12. 이러한 연산자에서는 값이 같은지 확인하는 대신 참조가 동일한지만 테스트하기 때문입니다.

13. 인수로 사용되는 형식에서 이러한 연산자를 오버로드한 경우에도 마찬가지입니다.

14. 다음 코드에서는 이러한 경우의 예를 보여 줍니다.

15. String 클래스에서 == 연산자를 오버로드해도 false가 출력됩니다.


public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}


16. 이러한 방식으로 동작하는 이유는 컴파일 시 컴파일러에는 T가 참조 형식이라는 정보만 제공되므로 모든 참조 형식에 유효한 기본 연산자가 사용되기 때문입니다.

17. 값이 동일한지 테스트하려면 where T : IComparable<T> 제약 조건을 함께 적용하고 제네릭 클래스를 생성하는 데 사용되는 모든 클래스에서 해당 인터페이스를 구현하는 것이 좋습니다.


여러 매개 변수 제한

1. 다음 예제와 같이 여러 매개 변수에 제약 조건을 적용하고 단일 매개 변수에 여러 제약 조건을 적용할 수 있습니다.


class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new() { }


바인딩되지 않은 형식 매개 변수

1. 공용 클래스 SampleClass<T>{}의 T와 같이 제약 조건이 없는 형식 매개 변수를 바인딩되지 않은 형식 매개 변수라고 합니다.

2. 바인딩되지 않은 형식 매개 변수에는 다음과 같은 규칙이 적용됩니다.

1) != 및 == 연산자를 사용할 수 없습니다. 구체적인 형식 인수에서 이러한 연산자를 지원하리라는 보장이 없기 때문입니다.

2) 바인딩되지 않은 형식 매개 변수와 System.Object 사이에 변환하거나 이 매개 변수를 임의의 인터페이스 형식으로 명시적으로 변환할 수 있습니다.

2) 바인딩되지 않은 형식 매개 변수를 null과 비교할 수 있습니다. 바인딩되지 않은 매개 변수를 null과 비교하는 경우 형식 인수가 값 형식이면 비교 결과로 항상 false가 반환됩니다.


제약 조건으로서의 형식 매개 변수

1. 일반 형식을 제약 조건으로 사용하면 다음 예제에서와 같이 자체 형식 매개 변수가 있는 멤버 함수에서 해당 매개 변수를 포함 형식의 형식 매개 변수로 제한해야 하는 경우에 유용합니다.


class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}


2. 위 예제에서 T는 Add 메서드의 컨텍스트에서는 형식 제약 조건이고 List 클래스의 컨텍스트에서는 바인딩되지 않은 형식 매개 변수입니다.

3. 형식 매개 변수는 제네릭 클래스 정의에서 제약 조건으로도 사용할 수 있습니다.

4. 형식 매개 변수는 다른 형식의 매개 변수와 함께 꺾쇠 괄호 안에 선언되어야 합니다.


//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }


5. 컴파일러에서는 형식 매개 변수가 System.Object에서 파생된다는 점을 제외하고는 이 제약 조건에 대해 어떠한 정보도 알 수 없으므로 형식 매개 변수와 제네릭 클래스를 함께 사용할 필요는 거의 없습니다.

6. 제네릭 클래스에 대해 형식 매개 변수를 제약 조건으로 사용하는 경우로는 두 형식 매개 변수 사이에 반드시 상속 관계가 있도록 정의하는 경우를 들 수 있습니다.




제네릭 클래스



1. 제네릭 클래스는 특정 데이터 형식에 고유하지 않은 작업을 캡슐화합니다.

2. 제네릭 클래스는 연결된 목록, 해시 테이블, 스택, 큐, 트리 등의 컬렉션에 가장 일반적으로 사용됩니다.

3. 컬렉션에서 항목을 추가하고 제거하는 등의 작업은 저장되는 데이터의 형식에 관계없이 기본적으로 동일한 방식으로 수행됩니다.

4. 컬렉션 클래스가 필요한 대부분의 시나리오에서는 .NET Framework 클래스 라이브러리에서 제공하는 컬렉션 클래스를 사용하는 것이 좋습니다.

5. 이러한 클래스 사용에 대한 자세한 내용은 .NET Framework 클래스 라이브러리의 제네릭(C# 프로그래밍 가이드)을 참조하십시오.

6. 일반적으로 기존의 구체적인 클래스로 시작하여 일반성과 편의성의 균형이 맞을 때까지 형식을 하나씩 형식 매개 변수로 바꾸는 방법으로 제네릭 클래스를 만듭니다.

7. 사용자 고유의 제네릭 클래스를 만들 때는 다음과 같은 사항을 고려해야 합니다.

1) 형식 매개 변수로 일반화할 형식.

ㄱ) 일반적으로는 매개 변수화할 수 있는 형식이 많을수록 코드의 융통성과 재사용 가능성이 향상됩니다.

ㄴ) 그러나 코드를 지나치게 일반화하면 다른 개발자가 읽거나 이해하기가 어려워집니다.

2) 형식 매개 변수에 적용할 제약 조건(형식 매개 변수에 대한 제약 조건(C# 프로그래밍 가이드) 참조).

ㄱ) 필요한 형식을 처리할 수 있는 범위 내에서 최대한 많은 제약 조건을 적용하는 것이 좋습니다.

ㄴ) 예를 들어 사용자 제네릭 클래스를 참조 형식으로만 사용하려는 경우에는 클래스 제약 조건을 적용합니다.

ㄷ) 이렇게 하면 클래스를 값 형식으로 잘못 사용하는 것을 막을 수 있고, T에 as 연산자를 사용하여 null 값 여부를 확인할 수 있습니다.

3) 제네릭 동작을 기본 클래스와 서브클래스로 분할할지 여부.

ㄱ) 제네릭 클래스는 기본 클래스가 될 수 있으므로 제네릭이 아닌 클래스에 적용되는 디자인 고려 사항이 동일하게 적용됩니다.

ㄴ) 자세한 내용은 이 항목의 뒷부분에서 설명하는 제네릭 기본 클래스에서 상속하는 데 대한 규칙을 참조하십시오.

4) 제네릭 인터페이스를 하나 이상 구현할지 여부

ㄱ) 예를 들어 제네릭 기반 컬렉션의 항목을 만드는 데 사용될 클래스를 디자인하는 경우 IComparable<T>(T는 사용자 클래스 형식)과 같은 인터페이스를 구현해야 할 수 있습니다.

8. 간단한 제네릭 클래스의 예제를 보려면 제네릭 소개(C# 프로그래밍 가이드)를 참조하십시오.

9. 형식 매개 변수와 제약 조건에 대한 규칙은 제네릭 클래스 동작에서 특히 상속과 멤버 액세스 가능성에 대해 몇 가지 영향을 줍니다.

10. 계속하려면 몇 가지 용어를 이해하고 있어야 합니다.

11. Node<T>, 제네릭 클래스의 경우 클라이언트 코드에서는 형식 인수를 지정하여 폐쇄형 생성 형식(Node<int>)을 만드는 방법으로 클래스를 참조하거나, 제네릭 기본 클래스를 지정하는 경우와 같이 형식 매개 변수를 지정하지 않고 개방형 생성 형식(Node<T>)을 만드는 방법으로 클래스를 참조할 수 있습니다.

12. 제네릭 클래스는 구체적인 클래스, 폐쇄형 구성 클래스 또는 개방형 구성 기본 클래스에서 상속할 수 있습니다.


class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type 
class NodeOpen<T> : BaseNodeGeneric<T> { }


13. 제네릭이 아닌 구체적인 클래스는 폐쇄형 생성 기본 클래스에서는 상속할 수 있지만 개방형 생성 클래스 또는 형식 매개 변수에서는 상속할 수 없습니다.

14. 이는 런타임에 클라이언트 코드에서 기본 클래스를 인스턴스화할 때 필요한 형식 인수를 제공할 수 없기 때문입니다.


//No error
class Node1 : BaseNodeGeneric<int> { }

//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}


15. 개방형 생성 형식에서 상속하는 제네릭 클래스에서는 다음 코드와 같이 상속하는 클래스에서 공유하지 않는 모든 기본 클래스 형식 매개 변수에 대해 형식 인수를 제공해야 합니다.


class BaseNodeMultiple<T, U> { }

//No error
class Node4<T> : BaseNodeMultiple<T, int> { }

//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }

//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {} 


16. 개방형 생성 형식에서 상속하는 제네릭 클래스에서는 기본 형식에 대한 제약 조건을 포함하거나 암시하는 제약 조건을 지정해야 합니다.


class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }


17. 제네릭 형식은 다음과 같이 여러 형식 매개 변수 및 제약 조건을 사용할 수 있습니다.


class SuperKeyType<K, V, U>
    where U : System.IComparable<U>
    where V : new()
{ }


18. 개방형 생성 형식 및 폐쇄형 생성 형식은 메서드 매개 변수로 사용될 수 있습니다.


void Swap<T>(List<T> list1, List<T> list2)
{
    //code to swap items
}

void Swap(List<int> list1, List<int> list2)
{
    //code to swap items
}


19. 제네릭 클래스에서 인터페이스를 구현하면 이 클래스의 모든 인스턴스를 해당 인터페이스에 캐스팅할 수 있습니다.

20. 제네릭 클래스는 비가변적입니다.

21. 즉, 입력 매개 변수에서 List<BaseClass>를 지정하면 List<DerivedClass>를 제공하려고 할 때 컴파일 타임 오류가 발생합니다.



제네릭 인터페이스


1. 제네릭 컬렉션 클래스 또는 컬렉션의 항목을 나타내는 제네릭 클래스에 대한 인터페이스를 정의하는 것이 유용한 경우가 많습니다.

2. 값 형식에 대해 boxing 및 unboxing 연산을 하지 않으려면 제네릭 클래스에서 IComparable 대신 IComparable<T> 같은 제네릭 인터페이스를 사용하는 것이 좋습니다.

3. .NET Framework 클래스 라이브러리에는 System.Collections.Generic 네임스페이스의 컬렉션 클래스에 사용할 제네릭 인터페이스가 여러 개 정의되어 있습니다.

4. 인터페이스를 형식 매개 변수에 대한 제약 조건으로 지정한 경우 이 인터페이스를 구현하는 형식만 사용할 수 있습니다.

5. 다음 코드 예제에서는 GenericList<T>에서 파생되는 SortedList<T> 클래스를 보여 줍니다.

6. 자세한 내용은 제네릭 소개(C# 프로그래밍 가이드)를 참조하십시오. SortedList<T>는 where T : IComparable<T> 제약 조건을 추가합니다.

7. 따라서 SortedList<T>의 BubbleSort 메서드에서는 목록 요소에 대해 제네릭 CompareTo 메서드를 사용할 수 있습니다.

8. 이 예제에서 목록 요소는 IComparable<Person>을 구현하는 단순 클래스인 Person입니다.


//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
    protected Node head;
    protected Node current = null;

    // Nested class is also generic on T
    protected class Node
    {
        public Node next;
        private T data;  //T as private member datatype

        public Node(T t)  //T used in non-generic constructor
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data  //T as return type of property
        {
            get { return data; }
            set { data = value; }
        }
    }

    public GenericList()  //constructor
    {
        head = null;
    }

    public void AddHead(T t)  //T as method parameter type
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    // Implementation of the iterator
    public System.Collections.Generic.IEnumerator<T> GetEnumerator()
    {
        Node current = head;
        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    // IEnumerable<T> inherits from IEnumerable, therefore this class 
    // must implement both the generic and non-generic versions of 
    // GetEnumerator. In most cases, the non-generic method can 
    // simply call the generic method.
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
    // A simple, unoptimized sort algorithm that 
    // orders list elements from lowest to highest:

    public void BubbleSort()
    {
        if (null == head || null == head.Next)
        {
            return;
        }
        bool swapped;

        do
        {
            Node previous = null;
            Node current = head;
            swapped = false;

            while (current.next != null)
            {
                //  Because we need to call this method, the SortedList
                //  class is constrained on IEnumerable<T>
                if (current.Data.CompareTo(current.next.Data) > 0)
                {
                    Node tmp = current.next;
                    current.next = current.next.next;
                    tmp.next = current;

                    if (previous == null)
                    {
                        head = tmp;
                    }
                    else
                    {
                        previous.next = tmp;
                    }
                    previous = tmp;
                    swapped = true;
                }
                else
                {
                    previous = current;
                    current = current.next;
                }
            }
        } while (swapped);
    }
}

// A simple class that implements IComparable<T> using itself as the 
// type argument. This is a common design pattern in objects that 
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
    string name;
    int age;

    public Person(string s, int i)
    {
        name = s;
        age = i;
    }

    // This will cause list elements to be sorted on age values.
    public int CompareTo(Person p)
    {
        return age - p.age;
    }

    public override string ToString()
    {
        return name + ":" + age;
    }

    // Must implement Equals.
    public bool Equals(Person p)
    {
        return (this.age == p.age);
    }
}

class Program
{
    static void Main()
    {
        //Declare and instantiate a new generic SortedList class.
        //Person is the type argument.
        SortedList<Person> list = new SortedList<Person>();

        //Create name and age values to initialize Person objects.
        string[] names = new string[] 
        { 
            "Franscoise", 
            "Bill", 
            "Li", 
            "Sandra", 
            "Gunnar", 
            "Alok", 
            "Hiroyuki", 
            "Maria", 
            "Alessandro", 
            "Raul" 
        };

        int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };

        //Populate the list.
        for (int x = 0; x < 10; x++)
        {
            list.AddHead(new Person(names[x], ages[x]));
        }

        //Print out unsorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with unsorted list");

        //Sort the list.
        list.BubbleSort();

        //Print out sorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with sorted list");
    }
}


9. 단일 형식에 다음과 같이 여러 인터페이스를 제약 조건으로 지정할 수 있습니다.


class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}


10. 인터페이스에서는 다음과 같이 두 개 이상의 형식 매개 변수를 정의할 수 있습니다.


interface IDictionary<K, V>
{
}


11. 클래스에 적용되는 상속 규칙이 인터페이스에도 적용됩니다.


interface IMonth<T> { }

interface IJanuary     : IMonth<int> { }  //No error
interface IFebruary<T> : IMonth<int> { }  //No error
interface IMarch<T>    : IMonth<T> { }    //No error
//interface IApril<T>  : IMonth<T, U> {}  //Error


12. 제네릭 인터페이스에 반공변성이 있는 경우, 즉 형식 매개 변수만 반환 값으로 사용하는 경우 제네릭 인터페이스는 제네릭이 아닌 인터페이스에서 상속할 수 있습니다.

13. .NET Framework 클래스 라이브러리에서 IEnumerable<T>은 GetEnumerator의 반환 값 및 Current 속성 getter에 T만 사용하므로 IEnumerable<T>은 IEnumerable에서 상속합니다.

14. 구체적인 클래스에서는 다음과 같이 폐쇄형 구성 인터페이스를 구현할 수 있습니다.


interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }


15. 제네릭 클래스는 클래스 매개 변수 목록에서 인터페이스에 필요한 모든 인수를 제공하는 경우 다음과 같이 제네릭 인터페이스 또는 폐쇄형 구성 인터페이스를 구현할 수 있습니다.


interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }

class SampleClass1<T> : IBaseInterface1<T> { }          //No error
class SampleClass2<T> : IBaseInterface2<T, string> { }  //No error


16. 제네릭 클래스, 제네릭 구조체 또는 제네릭 인터페이스 내의 메서드에는 메서드 오버로드를 제어하는 규칙이 동일하게 적용됩니다.

17. 자세한 내용은 제네릭 메서드(C# 프로그래밍 가이드)를 참조하십시오.




제네릭 메서드


1. 제네릭 메서드는 다음과 같이 형식 매개 변수를 사용하여 선언한 메서드입니다.


static void Swap<T>(ref T lhs, ref T rhs)
{
    T temp;
    temp = lhs;
    lhs = rhs;
    rhs = temp;
}


2. 다음 코드 예제에서는 형식 인수에 int를 사용하여 메서드를 호출하는 방법을 보여 줍니다.


public static void TestSwap()
{
    int a = 1;
    int b = 2;

    Swap<int>(ref a, ref b);
    System.Console.WriteLine(a + " " + b);
}


3. 형식 인수를 생략하고 컴파일러에서 이를 자동으로 유추하도록 할 수도 있습니다. Swap에 대한 다음 호출은 위 예제의 호출과 동일한 작업을 수행합니다.


Swap(ref a, ref b);


4. 정적 메서드와 인스턴스 메서드에는 형식 유추를 위한 동일한 규칙이 적용됩니다.

5. 컴파일러에서는 사용자가 전달하는 메서드 인수를 기준으로 형식 매개 변수를 유추할 수 있지만 제약 조건이나 반환 값만으로는 형식 매개 변수를 유추할 수 없습니다.

6. 따라서 매개 변수가 없는 메서드에 대해서는 형식 유추가 실행되지 않습니다.

7. 형식 유추는 컴파일러에서 오버로드된 메서드 시그니처를 확인하려고 하기 전에 컴파일 타임에 진행됩니다.

8. 컴파일러는 동일한 이름을 공유하는 모든 제네릭 메서드에 형식 유추 논리를 적용합니다.

9. 오버로드 확인 단계에서 컴파일러는 형식 유추에 성공한 제네릭 메서드만 포함합니다.

10. 제네릭 클래스 내에서 제네릭이 아닌 메서드는 다음과 같이 클래스 수준 형식 매개 변수에 액세스할 수 있습니다.


class SampleClass<T>
{
    void Swap(ref T lhs, ref T rhs) { }
}


11. 포함하는 클래스와 동일한 형식 매개 변수를 사용하는 제네릭 메서드를 정의하면 컴파일러에서 CS0693 경고가 발생합니다.

12. 이는 메서드 범위 내에서 내부 T에 대해 제공한 인수가 외부 T에 대해 제공한 인수를 숨기기 때문입니다.

13. 클래스를 인스턴스화할 때 제공한 인수가 아닌 다른 형식 인수를 사용하여 제네릭 클래스 메서드를 호출할 수 있으려면 다음 예제의 GenericList2<T>에서와 같이 메서드의 형식 매개 변수에 다른 식별자를 제공하는 것이 좋습니다.


class GenericList<T>
{
    // CS0693
    void SampleMethod<T>() { }
}

class GenericList2<T>
{
    //No warning
    void SampleMethod<U>() { }
}


14. 메서드에서 형식 매개 변수에 대한 더 구체적인 작업을 수행하려면 제약 조건을 사용합니다. SwapIfGreater<T>라는 이 버전의 Swap<T>은 IComparable<T>을 구현하는 형식 인수와만 함께 사용할 수 있습니다.


void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
    T temp;
    if (lhs.CompareTo(rhs) > 0)
    {
        temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}


15. 제네릭 메서드는 여러 형식 매개 변수에 대해 오버로드할 수 있습니다.

16. 예를 들어 다음 메서드는 모두 동일한 클래스에 지정할 수 있습니다.


void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }




제네릭 및 배열


1. C# 2.0 이상에서 하한이 0인 1차원 배열은 자동으로 IList<T>을 구현합니다.

2. 이러한 특성으로 인해 동일한 코드를 사용하여 배열 및 기타 컬렉션 형식에서 반복할 수 있는 제네릭 메서드를 만들 수 있습니다.

3. 이 기술은 주로 컬렉션에서 데이터를 읽는 데 유용합니다.

4. IList<T> 인터페이스는 배열에서 요소를 추가하거나 제거하는 데 사용할 수 없습니다.

5. 이 컨텍스트에서 배열에 RemoveAt과 같은 IList<T> 메서드를 호출하려고 하면 예외가 throw됩니다.

6. 다음 코드 예제에서는 IList<T> 입력 매개 변수를 사용하는 단일 제네릭 메서드에서 목록과 배열(이 경우 정수 배열) 모두를 통해 반복하는 방법을 보여 줍니다.


class Program
{
    static void Main()
    {
        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 returns True for the array and False for the List.
        System.Console.WriteLine
            ("IsReadOnly returns {0} for this collection.",
            coll.IsReadOnly);

        // The following statement causes a run-time exception for the 
        // array, but not for the List.
        //coll.RemoveAt(4);

        foreach (T item in coll)
        {
            System.Console.Write(item.ToString() + " ");
        }
        System.Console.WriteLine();
    }
}



제네릭 대리자


1. 대리자는 자체 형식 매개 변수를 정의할 수 있습니다.

2. 제네릭 대리자를 참조하는 코드에서는 제네릭 클래스를 인스턴스화하거나 제네릭 메서드를 호출할 때와 마찬가지로 다음 예제와 같이 형식 매개 변수를 지정하여 폐쇄형 구성 형식을 만들 수 있습니다.


public delegate void Del<T>(T item);
public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);


3. C# 버전 2.0에는 메서드 그룹 변환이라는 새로운 기능이 있습니다.

4. 이 기능은 제네릭 대리자뿐만 아니라 구체적인 대리자에도 적용되며, 이를 통해 위 코드를 다음과 같이 단순화된 구문으로 작성할 수 있습니다.


Del<int> m2 = Notify;


5. 제네릭 클래스 내에 정의된 대리자에서는 클래스 메서드와 마찬가지로 제네릭 클래스 형식 매개 변수를 사용할 수 있습니다.


class Stack<T>
{
    T[] items;
    int index;

    public delegate void StackDelegate(T[] items);
}


6. 대리자를 참조하는 코드에서는 다음과 같이 포함하는 클래스의 형식 매개 변수를 지정해야 합니다.


private static void DoWork(float[] items) { }

public static void TestStack()
{
    Stack<float> s = new Stack<float>();
    Stack<float>.StackDelegate d = DoWork;
}


7. sender 인수에 강력한 형식을 사용할 수 있고 Object 사이에서 캐스팅할 필요가 없으므로 제네릭 대리자는 특히 일반적인 디자인 패턴을 기반으로 이벤트를 정의할 때 유용합니다.


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;
}




제네릭 코드의 default 키워드


1. 제네릭 클래스 및 메서드에서 다음과 같은 내용을 미리 알지 못하는 경우 매개 변수화된 형식 T에 기본값을 할당하는 방법에 대한 문제가 발생합니다.

1) T가 참조 형식인지 값 형식인지 여부

2) T가 값 형식인 경우 숫자 값인지 구조체인지 여부

2. t가 매개 변수화된 형식 T의 변수인 경우 "t = null"과 같은 구문은 T가 참조 형식인 경우에만 유효하고 "t = 0"은 구조체가 아닌 숫자 값 형식인 경우에만 사용할 수 있습니다.

3. 참조 형식에 대해서는 null을 반환하고 숫자 값 형식에는 0을 반환하는 default 키워드를 사용하면 이 문제를 해결할 수 있습니다.

4. 구조체에 대해서는 멤버가 값 형식인지 참조 형식인지 여부에 따라 0 또는 null로 초기화된 구조체의 각 멤버가 반환됩니다.

5. null 허용 값 형식의 경우 default는 다른 구조체와 같이 초기화되는 System.Nullable<T>을 반환합니다.

6. GenericList<T> 클래스에 있는 다음 예제에서는 default 키워드를 사용하는 방법을 보여 줍니다.

7. 자세한 내용은 제네릭 개요를 참조하십시오.


namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Test with a non-empty list of integers.
            GenericList<int> gll = new GenericList<int>();
            gll.AddNode(5);
            gll.AddNode(4);
            gll.AddNode(3);
            int intVal = gll.GetLast();
            // The following line displays 5.
            System.Console.WriteLine(intVal);

            // Test with an empty list of integers.
            GenericList<int> gll2 = new GenericList<int>();
            intVal = gll2.GetLast();
            // The following line displays 0.
            System.Console.WriteLine(intVal);

            // Test with a non-empty list of strings.
            GenericList<string> gll3 = new GenericList<string>();
            gll3.AddNode("five");
            gll3.AddNode("four");
            string sVal = gll3.GetLast();
            // The following line displays five.
            System.Console.WriteLine(sVal);

            // Test with an empty list of strings.
            GenericList<string> gll4 = new GenericList<string>();
            sVal = gll4.GetLast();
            // The following line displays a blank line.
            System.Console.WriteLine(sVal);
        }
    }

    // T is the type of data stored in a particular instance of GenericList.
    public class GenericList<T>
    {
        private class Node
        {
            // Each node has a reference to the next node in the list.
            public Node Next;
            // Each node holds a value of type T.
            public T Data;
        }

        // The list is initially empty.
        private Node head = null;

        // Add a node at the beginning of the list with t as its data value.
        public void AddNode(T t)
        {
            Node newNode = new Node();
            newNode.Next = head;
            newNode.Data = t;
            head = newNode;
        }

        // The following method returns the data value stored in the last node in
        // the list. If the list is empty, the default value for type T is
        // returned.
        public T GetLast()
        {
            // The value of temp is returned as the value of the method. 
            // The following declaration initializes temp to the appropriate 
            // default value for type T. The default value is returned if the 
            // list is empty.
            T temp = default(T);

            Node current = head;
            while (current != null)
            {
                temp = current.Data;
                current = current.Next;
            }
            return temp;
        }
    }
}









'프로그래밍 > C#' 카테고리의 다른 글

클래스 및 구조체  (0) 2016.08.05
인터페이스, 이벤트, 인덱서  (0) 2016.08.04
LINQ 정리  (1) 2016.07.28
형 변환 예제  (0) 2016.07.10
StringBuilder 클래스  (0) 2016.07.09
:
Posted by 지훈2