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


제약 조건은 형식 인수에서 갖추고 있어야 하는 기능을 컴파일러에 알립니다. 제약 조건이 없으면 형식 인수가 어떤 형식이든 될 수 있습니다. 컴파일러는 모든 .NET 형식의 궁극적인 기본 클래스인 Object의 멤버만 가정할 수 있습니다. 자세한 내용은 제약 조건을 사용하는 이유를 참조하세요. 클라이언트 코드에서 제약 조건에 의해 허용되지 않는 형식을 사용하여 클래스를 인스턴스화하려고 하면 컴파일 시간 오류가 발생합니다. 제약 조건은 where 상황별 키워드를 사용하여 지정됩니다. 다음 표에는 7가지 형식의 제약 조건이 나와 있습니다.

제약 조건설명
where T : struct형식 인수는 값 형식이어야 합니다. Nullable를 제외한 임의의 값 형식을 지정할 수 있습니다. 자세한 내용은 Nullable 형식 사용을 참조하세요.
where T : class형식 인수는 참조 형식이어야 합니다. 이 제약 조건은 모든 클래스, 인터페이스, 대리자 또는 배열 형식에도 적용됩니다.
where T : unmanaged형식 인수는 참조 형식일 수 없으며, 모든 중첩 수준에서 참조 형식 멤버를 포함할 수 없습니다.
where T : new()형식 인수에 매개 변수가 없는 public 생성자가 있어야 합니다. 다른 제약 조건과 함께 사용할 경우 new() 제약 조건을 마지막에 지정해야 합니다.
where T : <기본 클래스 이름>형식 인수가 지정된 기본 클래스이거나 지정된 기본 클래스에서 파생되어야 합니다.
where T : <인터페이스 이름>형식 인수가 지정된 인터페이스이거나 지정된 인터페이스를 구현해야 합니다. 여러 인터페이스 제약 조건을 지정할 수 있습니다. 제약 인터페이스가 제네릭일 수도 있습니다.
where T : UT에 대해 제공되는 형식 인수는 U에 대해 제공되는 인수이거나 이 인수에서 파생되어야 합니다.

일부 제약 조건은 상호 배타적입니다. 모든 값 형식에는 매개 변수가 없는 액세스 가능 생성자가 있어야 합니다. struct 제약 조건은 new() 제약 조건을 의미하고, new() 제약 조건은 struct 제약 조건과 결합할 수 없습니다. unmanaged 제약 조건은 struct 제약 조건을 의미합니다. unmanaged 제약 조건은 struct 또는 new() 제약 조건과 결합할 수 없습니다.


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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System;
 
namespace TestProject
{
 
    public interface IIKs
    {
 
    }
 
    public class hy : IIKs
    {
 
        public hy(int s)
        {
 
 
        }
        public hy() => _s = 30;
 
        public override string ToString() => "hy Type !!!!!\t" + _s;
 
        int _s;
    }
 
 
    public class Node<T>
    {
        public Node(T t) => (Next, Data) = (null, t);
 
        public Node<T> Next { get; set; }
 
        T _data;
        public T Data {
            get {
                return _data;
            }
            set {
                _data = value;
            }
        }
    }
 
 
    //T 는 IIKs 를 상속받으면서 기본생성자가 존재해야한다
    public class Car<T> where T : IIKs , new()
    {
        public T ti = new T();
    }
 
 
    class t1s
    {
 
        static void Main(string[] args)
        {
            Car<hy> c = new Car<hy>();
 
            Console.WriteLine(c.ti.ToString());
            
        }
 
    }
}
 

cs


결과 화면
hy Type !!!!!   30

제약 조건을 사용하는 이유

형식 매개 변수 제약을 통해 허용되는 작업 및 메서드 호출 수를 제약 형식 및 해당 상속 계층 구조의 모든 형식에서 지원하는 작업 및 메서드 호출로 늘립니다. 제네릭 클래스 또는 메서드를 디자인할 때 제네릭 멤버에서 단순 할당 이외의 작업을 대해 수행하거나 System.Object에서 지원하지 않는 메서드를 호출하는 경우 형식 매개 변수에 제약 조건을 적용해야 합니다. 예를 들어 기본 클래스 제약 조건은 이 형식의 개체나 이 형식에서 파생된 개체만 형식 인수로 사용된다고 컴파일러에 알립니다. 컴파일러에 이 보장이 있으면 해당 형식의 메서드가 제네릭 클래스에서 호출되도록 허용할 수 있습니다. 다음 코드 예제에서는 기본 클래스 제약 조건을 적용하여 GenericList<T> 클래스(제네릭 소개에 있음)에 추가할 수 있는 기능을 보여 줍니다.

C#
public class Employee
{
    public Employee(string s, int i) => (Name, ID) = (s, i);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);        //이 부분은 식 본문 정의글 참고 : http://3dmpengines.tistory.com/2004

        public Node Next { get; set; }
        public T Data { get; set; }
    }

    private Node head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { 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;
    }
}

이 제약 조건을 통해 제네릭 클래스에서 Employee.Name 속성을 사용할 수 있습니다. 제약 조건은 T 형식의 모든 항목을 Employee개체 또는 Employee에서 상속하는 개체 중 하나로 보장하도록 지정합니다.

동일한 형식 매개 변수에 여러 개의 제약 조건을 적용할 수 있으며, 제약 조건 자체가 다음과 같이 제네릭 형식일 수 있습니다.

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

where T : class 제약 조건을 적용하는 경우 == 및 != 연산자는 참조 ID만 테스트하고 값이 같은지 테스트하지 않으므로 형식 매개 변수에 사용하지 않도록 합니다. 이러한 연산자가 인수로 사용되는 형식에서 오버로드되는 경우에도 이 동작이 발생합니다. 다음 코드는 이 내용을 보여 줍니다. String 클래스가 == 연산자를 오버로드하지만 출력이 false입니다.

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

컴파일러에서 컴파일 시간에 T가 참조 형식이고 모든 참조 형식에 유효한 기본 연산자를 사용해야 한다는 것만 인식합니다. 값 일치 여부를 테스트해야 하는 경우에도 where T : IEquatable<T> 또는 where T : IComparable<T> 제약 조건을 적용하고 제네릭 클래스를 생성하는 데 사용할 모든 클래스에서 인터페이스를 구현하는 것이 좋습니다.

여러 매개 변수 제한

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

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

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

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

  • != 및 == 연산자는 구체적인 형식 인수가 이러한 연산자를 지원한다는 보장이 없기 때문에 사용할 수 없습니다.
  • System.Object로/에서 변환하거나 임의의 인터페이스 형식으로 명시적으로 변환할 수 있습니다.
  • null과 비교할 수 있습니다. 바인딩되지 않은 매개 변수를 null과 비교하는 경우 형식 인수가 값 형식이면 비교에서 항상 false를 반환합니다.

제약 조건으로 형식 매개 변수 사용

다음 예제와 같이 고유한 형식 매개 변수가 있는 멤버 함수가 해당 매개 변수를 포함 형식의 형식 매개 변수로 제약해야 하는 경우 제네릭 형식 매개 변수를 제약 조건으로 사용하면 유용합니다.

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

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

제네릭 클래스 정의에서 형식 매개 변수를 제약 조건으로 사용할 수도 있습니다. 형식 매개 변수는 다른 형식 매개 변수와 함께 꺾쇠괄호 안에 선언해야 합니다.

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

컴파일러에서 형식 매개 변수가 System.Object에서 파생된다는 점을 제외하고는 형식 매개 변수에 대해 아무 것도 가정할 수 없기 때문에, 제네릭 클래스에서 형식 매개 변수를 제약 조건으로 사용하는 경우는 제한됩니다. 두 형식 매개 변수 사이의 상속 관계를 적용하려는 시나리오에서 제네릭 클래스에 형식 매개 변수를 제약 조건으로 사용합니다.

관리되지 않는 제약 조건

C# 7.3부터 unmanaged 제약 조건을 사용하여 형식 매개 변수가 관리되지 않는 형식이어야 한다고 지정할 수 있습니다. 관리되지 않는 형식은 참조 형식이 아니며, 모든 중첩 수준에서 참조 형식 필드를 포함하지 않는 형식입니다. unmanaged 제약 조건을 사용하면 다음 예제와 같이 메모리 블록으로 조작할 수 있는 형식을 사용하도록 재사용 가능한 루틴을 작성할 수 있습니다.

C#
unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

앞의 메서드는 기본 제공 형식으로 알려지지 않은 형식에서 sizeof 연산자를 사용하므로 unsafe 컨텍스트에서 컴파일해야 합니다. unmanaged 제약 조건이 없으면 sizeof 연산자를 사용할 수 없습니다.

대리자 제약 조건

C# 7.3부터 System.Delegate 또는 System.MulticastDelegate를 기본 클래스 제약 조건으로 사용할 수도 있습니다. CLR에서는 항상 이 제약 조건을 허용했지만, C# 언어에서는 이 제약 조건을 허용하지 않았습니다. System.Delegate 제약 조건을 사용하면 형식이 안전한 방식으로 대리자에서 작동하는 코드를 작성할 수 있습니다. 다음 코드는 두 대리자가 동일한 형식인 경우 이를 결합하는 확장 메서드를 정의합니다.

C#
public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

위의 메서드를 사용하여 동일한 형식의 대리자를 결합할 수 있습니다.

C#
Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);
combined();

Func<bool> test = () => true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

마지막 줄의 주석 처리를 제거하면 컴파일되지 않습니다. first과 test는 모두 대리자 형식이지만 서로 다른 대리자 형식입니다.

열거형 제약 조건

C# 7.3부터 System.Enum 형식을 기본 클래스 제약 조건으로 지정할 수도 있습니다. CLR에서는 항상 이 제약 조건을 허용했지만, C# 언어에서는 이 제약 조건을 허용하지 않았습니다. System.Enum을 사용하는 제네릭은 System.Enum의 정적 메서드를 사용하여 결과를 캐시하기 위해 형식이 안전한 프로그래밍을 제공합니다. 다음 샘플에서는 열거형 형식에 유효한 값을 모두 찾은 다음, 해당 값을 문자열 표현에 매핑하는 사전을 작성합니다.

C#
public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

사용된 메서드는 성능에 영향을 주는 리플렉션을 사용합니다. 리플렉션이 필요한 호출을 반복하는 대신, 이 메서드를 호출하여 캐시되고 다시 사용되는 컬렉션을 작성할 수 있습니다.

다음 샘플과 같이 이 메서드는 열거형을 만들고 해당 값과 이름의 사전을 작성하는 데 사용할 수 있습니다.

C#
enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
C#
var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)
    Console.WriteLine($"{pair.Key}:\t{pair.Value}");



ref :  https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters


+ Recent posts