C# 11: Generic Attribute
지금까지 C#의 Attribute에서는 제네릭(Generics)을 사용할 수 없었다. C# 11에서는 Attribute 에서 파생되는 Custom Attribute 클래스에서 제네릭을 사용할 수 있도록 하였다. 즉, 이제 Attribute에서 제네릭을 사용하여 하나 이상의 타입 파라미터를 지정할 수 있으며, Reflection을 사용하여 Attribute 메타정보에서 타입 정보를 읽어 활용할 수 있게 되었다.
C# 11 이전 버전에서 이러한 타입 정보는 Attribute 생성자에 전달하였고, 이를 Attribute 필드에 저장하여 사용하였다. 예를 들어, 아래 [예제A]는 C# 11 이전 버전에서 Attribute 생성자를 통해 타입 정보를 저장하는 예이고, [예제B]는 C# 11에서 제네릭을 사용하여 타입 정보를 지정하는 예이다.
Generic Attribute를 사용하면 타입 정보를 간단하게 지정할 수 있을 뿐만 아니라, 또한 타입 파라미터에 대해 제약(Generic Constraint)을 지정하여 어떤 타입들이 지정될 수 있는 지를 정할 수 있다. C# 11 이전에는 생성자를 통해 Type 을 전달하였는데 이러한 방식은 컴파일 타임에 타입을 체크할 수 없는 반면, C# 11에는 제네릭의 Constraint를 사용함으로써 컴파일 타임에 보다 구체적으로 타입을 체크할 수 있게 되었다.
C# 11 이전 버전에서 이러한 타입 정보는 Attribute 생성자에 전달하였고, 이를 Attribute 필드에 저장하여 사용하였다. 예를 들어, 아래 [예제A]는 C# 11 이전 버전에서 Attribute 생성자를 통해 타입 정보를 저장하는 예이고, [예제B]는 C# 11에서 제네릭을 사용하여 타입 정보를 지정하는 예이다.
Generic Attribute를 사용하면 타입 정보를 간단하게 지정할 수 있을 뿐만 아니라, 또한 타입 파라미터에 대해 제약(Generic Constraint)을 지정하여 어떤 타입들이 지정될 수 있는 지를 정할 수 있다. C# 11 이전에는 생성자를 통해 Type 을 전달하였는데 이러한 방식은 컴파일 타임에 타입을 체크할 수 없는 반면, C# 11에는 제네릭의 Constraint를 사용함으로써 컴파일 타임에 보다 구체적으로 타입을 체크할 수 있게 되었다.
예제
// [예제A] C# 11 이전:
// 타입 정보를 갖는 Attribute 정의
class ValidatorAttribute : Attribute
{
private readonly Type type;
public ValidatorAttribute(Type type)
{
this.type = type;
}
public Type ValidatorType => type;
}
// 생성자 파라미터에 typeof를 사용하여 타입 전달
[Validator(typeof(Customer))]
class Customer
{
public int Id;
public string Name;
public string Email;
}
// [예제B] C# 11 Generic Attribute:
// Attribute에 제네릭 타입 T를 지정.
// (추가적으로 타입 T의 Constrait를 지정할 수도 있다)
class ValidatorAttribute<T> : Attribute
where T : class
{
}
// Attribute를 사용할 때 타입을 지정
[Validator<Customer>]
class Customer
{
public int Id;
public string Name;
public string Email;
}
// 타입 정보를 갖는 Attribute 정의
class ValidatorAttribute : Attribute
{
private readonly Type type;
public ValidatorAttribute(Type type)
{
this.type = type;
}
public Type ValidatorType => type;
}
// 생성자 파라미터에 typeof를 사용하여 타입 전달
[Validator(typeof(Customer))]
class Customer
{
public int Id;
public string Name;
public string Email;
}
// [예제B] C# 11 Generic Attribute:
// Attribute에 제네릭 타입 T를 지정.
// (추가적으로 타입 T의 Constrait를 지정할 수도 있다)
class ValidatorAttribute<T> : Attribute
where T : class
{
}
// Attribute를 사용할 때 타입을 지정
[Validator<Customer>]
class Customer
{
public int Id;
public string Name;
public string Email;
}
C# 11: Generic Attribute 정보 사용
Generic Attribute는 Attribute의 메타정보에 더하여 추가적으로 타입 정보를 저장한다. 이 타입 정보는 .NET Reflection을 사용하여 읽혀 질 수 있고, 이를 기반으로 다른 코드를 실행하도록 할 수 있다.
아래 예제는 위에서 정의한 ValidatorAttribute를 사용하여 타입 T에 지정된 클래스 타입에 따라 라는 Validator 객체를 생성하는 샘플 코드이다.
아래 예제는 위에서 정의한 ValidatorAttribute를 사용하여 타입 T에 지정된 클래스 타입에 따라 라는 Validator 객체를 생성하는 샘플 코드이다.
예제
// [예제A] C# 11 이전:
public IValidator GetValidator(object obj)
{
// 해당 객체가 ValidatorAttribute를 가지고 있는 지 체크
var attr = (ValidatorAttribute) obj.GetType()
.GetCustomAttributes(typeof(ValidatorAttribute), true)
.FirstOrDefault();
if (attr != null && attr.ValidatorType != null)
{
// ValidatorAttribute가 있으면 ValidatorType 별로
// 다른 Validator를 생성하여 리턴함.
if (attr.ValidatorType == typeof(Customer))
{
return (IValidator) Activator.CreateInstance(typeof(CustomerValidator));
}
// else if ...
}
return null;
}
interface IValidator { }
class CustomerValidator : IValidator { }
// [예제B] C# 11 Generic Attribute:
public IValidator GetValidator(object obj)
{
// 해당 객체가 ValidatorAttribute를 가지고 있는 지 체크
var attr = obj.GetType()
.GetCustomAttributes(typeof(ValidatorAttribute<>), true)
.FirstOrDefault();
if (attr != null)
{
// ValidatorAttribute가 있으면 GenericTypeArguments를 체크하여
// 타입 정보를 읽어, 이에 기초하여 적절한 Validator를 생성함.
var validatorType = attr.GetType().GenericTypeArguments.First();
if (validatorType == typeof(Customer))
{
return (IValidator)Activator.CreateInstance(typeof(CustomerValidator));
}
// else if ...
}
return null;
}
public IValidator GetValidator(object obj)
{
// 해당 객체가 ValidatorAttribute를 가지고 있는 지 체크
var attr = (ValidatorAttribute) obj.GetType()
.GetCustomAttributes(typeof(ValidatorAttribute), true)
.FirstOrDefault();
if (attr != null && attr.ValidatorType != null)
{
// ValidatorAttribute가 있으면 ValidatorType 별로
// 다른 Validator를 생성하여 리턴함.
if (attr.ValidatorType == typeof(Customer))
{
return (IValidator) Activator.CreateInstance(typeof(CustomerValidator));
}
// else if ...
}
return null;
}
interface IValidator { }
class CustomerValidator : IValidator { }
// [예제B] C# 11 Generic Attribute:
public IValidator GetValidator(object obj)
{
// 해당 객체가 ValidatorAttribute를 가지고 있는 지 체크
var attr = obj.GetType()
.GetCustomAttributes(typeof(ValidatorAttribute<>), true)
.FirstOrDefault();
if (attr != null)
{
// ValidatorAttribute가 있으면 GenericTypeArguments를 체크하여
// 타입 정보를 읽어, 이에 기초하여 적절한 Validator를 생성함.
var validatorType = attr.GetType().GenericTypeArguments.First();
if (validatorType == typeof(Customer))
{
return (IValidator)Activator.CreateInstance(typeof(CustomerValidator));
}
// else if ...
}
return null;
}
C# 11: Generic Attribute 타입 아규먼트 제약
Generic Attribute는 타입 T 아규먼트에 다음과 같은 타입을 사용할 수 없다:
(1) dynamic
(2) string? (또는 null reference type)
(3) (int x, int y) 포맷의 튜플
위와 같은 타입을 사용할 수는 없지만, 대체 타입으로 각각 object (dynamic의 기저 타입), string, ValueTuple<int, int> 등을 사용할 수 있다.
(1) dynamic
(2) string? (또는 null reference type)
(3) (int x, int y) 포맷의 튜플
위와 같은 타입을 사용할 수는 없지만, 대체 타입으로 각각 object (dynamic의 기저 타입), string, ValueTuple<int, int> 등을 사용할 수 있다.