본문 바로가기

진리는어디에/C#

[C#] Generic Constraints

들어가며

클래스에 제네릭을 적용할 때 제네릭 인자를 특정 클래스를 상속 받은 것만 사용 할 수 있도록 하고 싶은데 어떻게 해야 할지 몰라 찾아 보니 [여기]에 설명이 되어 있길래 번역해본다.

C#은 제네릭 타입을 사용 할 때 제약 사항을 줄 수 있다. 이 제약 사항들을 이용하여 제네릭 타입의 인스턴스를 생성하는 코드를 컴파일 타임에 에러를 찾아 낼 수 있다.

where 키워드를 이용해 제네릭 타입에 대해 하나 또는 이상의 제약사항을 설정 할 수 있다.

GenericTypeName<T> where T : constraint1, constraint2

아래 예제는 보여준다. 제약 사항과 함께하는 제네릭 클래스에 제약사항을 

class DataStore<T> where T : class
{
    public T Data { get; set; }
}

위에서, 우리는 클래스 제약사항을 적용했다. 이것은 참조된 타입만이 DataStore 클래스를 생성 시 제네릭 인자로 넘겨질수 있다는 것이다.  그래서 당신은 클래스, 인터페이스, 델리게이트 또는 배열 타입을 넘겨 줄 수 있다. 값 타입을 넘기는 것은 컴파일 에러를 발생 시킨다. 그래서 우리는 프리미티브 타입이나 struct 타입을 제네릭 인자로 사용 할 수는 없다.

DataStore<string> store = new DataStore<string>(); // valid
DataStore<MyClass> store = new DataStore<MyClass>(); // valid
DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // valid
DataStore<IEnumerable> store = new DataStore<IMyInterface>(); // valid
DataStore<ArrayList> store = new DataStore<ArrayList>(); // valid
//DataStore<int> store = new DataStore<int>(); // compile-time error

아래 테이블은 제네릭 타입의 제약 사항을 나열해 놓았다.

제약사항 설명
class 타입 인자는 반드시 class, interface, delegate 또는 배열 타입이어야한다
class? 타입 인자는 반드시 nullable 또는 non-nullable class, interface, delegate 또는 배열 타입이어야 한다.
struct 타입 인자는 반드시 non-nullable 값 타입이어야 한다. 예를 들어 int, char, bool, float와 같은기본 타입
new() 타입 인자는 반드시 public 인자 없는 생성자를 가지고 있는 레퍼런스 타입이어야 한다. 이 제약 조건은 struct나 unmanaged 제약 조건과 같이 사용 할 수 없다.
notnull C# 8.0 부터 사용 가능. 타입 인자는 non-nullable 참조 타입 또는 값 타입을 사용 할 수 있다. 만일 그렇지 않다면 컴파일러는 에러 대신 경고를 발생 시킨다.
unmanaged 타입 인자는 반드시 non-nullable unmanaged 타입이어야 한다.
base class 타입 인자는 반드시 지정된 클래스를 상속 받아야 한다. Object, Array, ValueType 클래스들은 베이스 클래스로 사용이 불가능 하다. Enum, Delegate, MulticastDelegate는 C# 7.3 이전 버전에서는 사용 불가하다.
<base class name>? 타입 인자는 반드시 지정된 nullable 또는 non-nullable 클래스 거나, 이 클래스를 상속 받은 클래스여야 한다.
<interface name> 타입 인자는 반드시 지정된 interface를 구현해야 한다.
<interface name>? 타입 인자는 반드시 지정된 interface를 구현해야 한다. 이것은 nullable 참조 타입 또는 non-nullable 참조 타입, 값 타입이 될 수 있다.
where T : U 타입 인자 T는 반드시 U를 상속 받거나 U여야 한다.

※ nullable? - https://kukuta.tistory.com/359

where T : struct

아래 예제는 non-nullable 값 타입인 struct 제약 사항에 대해 설명한다.

class DataStore<T> where T : struct
{
    public T Data { get; set; }
}

DataStore<int> store = new DataStore<int>(); // valid
DataStore<char> store = new DataStore<char>(); // valid
DataStore<MyStruct> store = new DataStore<MyStruct>(); // valid
//DataStore<string> store = new DataStore<string>(); // compile-time error 
//DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // compile-time error 
//DataStore<ArrayList> store = new DataStore<ArrayList>(); // compile-time error

where T : new()

아래 예제는 인자 없는 public 생성자를 가진 클래스 제약 사항에 대해 설명한다.

class DataStore<T> where T : class, new()
{
    public T Data { get; set; }
}

DataStore<MyClass> store = new DataStore<MyClass>(); // valid
DataStore<ArrayList> store = new DataStore<ArrayList>(); // valid
//DataStore<string> store = new DataStore<string>(); // compile-time error 
//DataStore<int> store = new DataStore<int>(); // compile-time error 
//DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // compile-time error

where T : baseclass

아래 예제는 base class 제약 사항에 대해 설명한다. base class는 지정된 class, abstract class 또는 인터페이스이거나 이를 상속 받은 클래스어야한다.

class DataStore<T> where T : IEnumerable
{
    public T Data { get; set; }
}

DataStore<ArrayList> store = new DataStore<ArrayList>(); // valid
DataStore<List> store = new DataStore<List>(); // valid
//DataStore<string> store = new DataStore<string>(); // compile-time error 
//DataStore<int> store = new DataStore<int>(); // compile-time error 
//DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // compile-time error 
//DataStore<MyClass> store = new DataStore<MyClass>(); // compile-time error
유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!