제네릭 제약 조건 추가
class GenericClass <T>
{
public T objMember { get; set; }
}
class Program
{
static void Main(string[] args)
{
GenericClass<int> genericObj1 = new GenericClass<int>();
GenericClass<string> genericObj2 = new GenericClass<string>();
GenericClass<ArrayList> genericObj3 = new GenericClass<ArrayList>();
}
}
제네릭은 모든 타입을 허용하는 기법이므로 GenericClass 클래스의 objMember 멤버 변수는 값 형식인 int형이 될 수도 있고 참조 형식인 string형과 ArrayList 타입도 가능하다.
"GenericClass 클래스의 objMember 멤버 변수는 값 형식만 가능하도록 구현"해야 한다는 제약 조건이 주어질 경우 참조 형식인 string형과 ArrayList타입은 objMember 멤버 변수의 타입으로 허용되지 않는다.
이러한 제약 조건을 구현하기 위해 GenericClass 클래스 선언문에 where 조건을 추가한다.
class GenericClass <T> where T : struct
{
public T objMember { get; set; }
}
여기서 struct는 구조체가 아니라 제네릭에서 값 형식만 허용한다는 제약 조건이다.
즉, GenericClass 클래스 멤버 변수 objMember는 값 형식만 타입으로 허용한다.
struct 제약 조건을 추가하고 다음 코드를 실행하면 컴파일 에러가 발생한다.
class GenericClass <T> where T : struct
{
public T objMember { get; set; }
}
class Program
{
static void Main(string[] args)
{
GenericClass<int> genericObj1 = new GenericClass<int>();
GenericClass<string> genericObj2 = new GenericClass<string>();
GenericClass<ArrayList> genericObj3 = new GenericClass<ArrayList>();
}
}
string 타입과 ArrayList는 참조 형식이므로 제약 조건에 위반되어 컴파일 에러가 발생한다.
제네릭 제약 조건 종류
제약 조건 | 설명 |
where T : struct | T는 null을 허용하지 않는 값 형식이어야 한다. |
where T : class | T는 참조 형식이어야 한다. |
where T : new() | T는 매개 변수가 없는 public 생성자가 있어야 한다. 다른 제약 조건과 함께 사용되는 경우 new() 제약 조건을 마지막에 지정해야 한다. |
where T : notnull | T는 null이 아닌 형식이어야 한다. |
where T : unmanaged | T는 null이 아닌 비관리형 형식이어야 한다. |
where T : <base class name> | T는 지정된 기본 클래스이거나 이 클래스에서 파생된 클래스여야 한다. |
where T : <interface name> | T는 지정된 기본 인터페이스이거나 이 인터페이스에서 파생된 유형이여야 한다. |
where T : U | U는 인터페이스, 추상 클래스 또는 일반 클래스가 될 수 있으며, T는 U에서 상속되어야 한다. |
new 제약 조건
new 제약 조건은 인스턴스를 생성하기 위해 사용되는 new 연산자와 동일하며, 데이터 타입에 기본 생성자가 존재하는 타입만 허용한다.
int intVar = new int();
// string 타입은 기본 생성자 없으므로 컴파일 에러
string strVar1 = new string();
// 문자열을 전달해서 값을 초기화하는 경우는 정상
string strVar2 = new string("ABC");
ArrayList arrList = new ArrayList();
int 형과 ArrayList는 기본 생성자가 존재하지만, string 타입은 기본 생성자가 존재하지 않기 때문에 컴파일 에러가 발생한다.
class GenericClass <T> where T : new()
{
public T objMember { get; set; }
}
class Program
{
static void Main(string[] args)
{
GenericClass<int> genericObj1 = new GenericClass<int>();
// 컴파일 에러
GenericClass<string> genericObj2 = new GenericClass<string>();
GenericClass<ArrayList> genericObj3 = new GenericClass<ArrayList>();
}
}
제네릭에서도 string 타입은 기본 생성자가 존재하지 않기 때문에 new 제약 조건이 있는 경우 컴파일 에러가 발생한다.
new 제약 조건은 다음 코드처럼 다른 제약 조건과 함께 사용할 수 있다.
class GenericClass <T> where T : class, new()
{
public T objMember { get; set; }
}
단, struct 제약 조건과는 사용할 수 없으며 new 제약 조건은 마지막에 위치해야 한다.
notnull 제약 조건
notnull 제약 조건은 C# 8.0부터 도입되었으며, null이 아닌 값 형식 또는 참조 형식으로 타입을 지정해야 한다.
다른 제약 조건과는 다르게 제약 조건을 위반하면, 컴파일 에러가 아니라 경고를 발생한다.
unmanaged 제약 조건
nmanaged 제약 조건은 관리가 되지 않는 타입들을 허용하는 제약 조건이다.
아래는 비관리형 타입이다.
- sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool
- 열거형
- 포인터
public struct TestStruct
{
public int X;
public ArrayList Y;
}
class GenericClass <T> where T : unmanaged
{
public T objMember { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 정상
GenericClass<int> genericObj1 = new GenericClass<int>();
// 컴파일 에러
GenericClass<TestStruct> genericObj2 = new GenericClass<TestStruct>();
}
}
TestStruct 구조체에서 비관리형 타입이 아닌 ArrayList를 타입으로 정의하여 컴파일 에러가 발생한다.
기반 클래스 이름 제약 조건
기반 클래스 이름 제약 조건은 제네릭 유형을 특정 클래스로 제한하며, 특정 클래스 또는 특정 클래스에서 파생된 클래스만 허용한다.
public class GenericClass<T> where T : SuperPerson
{
}
public class SuperPerson
{
}
public class Person : SuperPerson
{
}
class Program
{
static void Main(string[] args)
{
GenericClass<SuperPerson> superPersonObj = new GenericClass<SuperPerson>();
GenericClass<Person> personObj = new GenericClass<Person>();
}
}
제네릭 유형은 제약 조건인 SuperPerson 클래스 또는 SuperPerson 클래스에 파생된 Person 클래스로 설정할 수 있다.
인터페이스 이름 제약 조건
인터페이스 이름 제약 조건은 기반 클래스 이름 제약 조건과 유사하다.
인터페이스를 제네릭 유형으로 제한하며, 해당 인터페이스 또는 파생된 유형만 허용한다.
public class GenericClass<T> where T : IPerson
{
}
public interface IPerson
{
}
public class Person : IPerson
{
}
class Program
{
static void Main(string[] args)
{
GenericClass<IPerson> superPersonObj = new GenericClass<IPerson>();
GenericClass<Person> personObj = new GenericClass<Person>();
}
}
U 제약 조건
GenerClass 클래스의 제네릭 유형인 T는 U에 상속된다.
public class GenericClass<T, U> where T : U
{
public void DoWork(T subClass, U baseClass)
{
}
}
public interface IPerson
{
}
public class Person : IPerson
{
}
class Program
{
static void Main(string[] args)
{
GenericClass<Person, IPerson> genericObj = new GenericClass<Person, IPerson>();
}
}
멀티 제약 조건
멀티 제약 조건은 제네릭 클래스에 제네릭 유형이 2개 이상이며, 제네릭 유형마다 제약 조건이 존재하는 경우다.
public class GenericClass<T, X> where T : struct where X : class
{
}
제네릭 유형 T는 struct 제약 조건을 추가하였으며, X는 class 제약 조건을 추가하였다.
'프로그래밍 언어 > C#' 카테고리의 다른 글
C# 배열 복사 (0) | 2023.01.08 |
---|---|
C# 배열 초기화, 다차원배열, 가변배열에 대해서 (0) | 2023.01.07 |
C# string.format, 문자열 보간($)을 이용한 문자열 출력 방법 (0) | 2023.01.05 |
C# 난수 생성 Random 클래스 (0) | 2022.10.24 |
C# 구조체가 IEquatable<T>를 상속해야 하는 이유 (0) | 2022.08.21 |