.NET 환경에서 개발을 하다 보면 쉽게 접하는 Attribute와 Reflection에 대해 기록했습니다.

메타데이터(metadata or metainformation)는 데이터(data)에 대한 데이터이다.
데이터 베이스 작업을 할 때를 예로 들어보자.
데이터베이스 내부에 저장된 데이터는 실제 데이터이고,
테이블의 구조와 테이블 간의 관계는 메타데이터이다.
attribute는 타입(type)에 메타데이터를 추가한다.
즉, 기존 메타데이터에 타입이나 메서드에 대한 정보를 추가하는 방식인데,
이는 Type 객체에서 읽을 수 있는 유형이나 메서드를 설명한다.
reflection은 애플리케이션에 사용되는 유형(type)을 검사할 수 있는 코드를 작성할 수 있는 메커니즘이다.
예를 들어 리플렉션을 사용하면 특정 객체에 속하는 모든 필드와 해당 값을 나열할 수 있다.
리플렉션을 사용하면 런타임에 일부 유형에 대한 정보에 액세스 할 수 있다.
값뿐만 아니라 이름에도 접근할 수 있고, 메서드, 생성자, 액세스 한정자 등에 대한 정보에 액세스 할 수 있다.
메타데이터, attribute, relection을 이해하기 위해 다음의 코드를 보자.
다음의 코드는 Person이라는 클래스와 Name 속성의 유효성 검사를 위한 attribute, reflection을 통해 유효성 검사를 수행하는 Validator 클래스가 있다.
var validPerson = new Person("둥", 1975);
var validator = new Validator();
Console.WriteLine(validator.Validate(validPerson) ?
"Person is valid" :
"Person is invalid");
Console.ReadKey();
public class Person
{
[StringLengthValidate(2, 10)]
public string Name { get; } // 이름 길이 유효성 검사 2 ~ 10
public int YearOfBirth { get; }
public Person(string name, int yearOfBirth)
{
Name = name;
YearOfBirth = yearOfBirth;
}
public Person(string name)
{
Name = name;
}
}
// 모든 Property에 사용하고자 아래의 AttributeUsage라는 내장 속성을 사용한다.
[AttributeUsage(AttributeTargets.Property)]
class StringLengthValidateAttribute : Attribute
// 모든 사용자 Attribute는 Attribute 클래스에서 파생되어야 한다.
// 이름은 Attribute로 끝나야 한다.
// 실제로 속성을 사용할 때는 접미사(Attribute)가 생략된다.
{
public int Min { get; }
public int Max { get; }
public StringLengthValidateAttribute(int min, int max)
{
Min = min;
Max = max;
}
}
class Validator
{
public bool Validate(object obj)
{
var type = obj.GetType();
var propertiesToValidate = type
.GetProperties()
.Where(property =>
Attribute.IsDefined(
property, typeof(StringLengthValidateAttribute)));
foreach (var property in propertiesToValidate)
{
object? propertyValue = property.GetValue(obj);
if(propertyValue is not string)
{
throw new InvalidOperationException(
$"Attribute {nameof(StringLengthValidateAttribute)}" +
$" can only be applied to strings");
}
var value = (string)propertyValue;
var attribute = (StringLengthValidateAttribute)property.GetCustomAttributes(
typeof(StringLengthValidateAttribute), true ).First();
if( value.Length < attribute.Min || value.Length > attribute.Max)
{
Console.WriteLine($"Property {property.Name} is invalid."
+ Environment.NewLine
+ $"Value is {value}.");
return false;
}
}
return true;
}
}
결과:

이는 기본 .NET 클래스와 외부 라이브러리에서 널리 사용된다.
ASP .NET을 사용하다 보면 다양한 Attribute를 사용하게 된다.
당시에 어물쩍 넘어가며 "어떻게든 되니까 작동하겠지" 싶었는데, 내부적으로 작동하는 과정을 어느 정도 고민해 볼 수 있게 됐다.
🤔 attribute의 parameter에 대해서
위 StringLengthValidate는 int 자료형 매개변수 두 개를 인자로 받도록 되어있다.
그러면, List도 매개변수로 받을 수 있을까?
결론을 바로 이야기하자면 안된다.

유효한 attributes parameters type들은 다음과 같다.
1. 간단한 타입들 : bool, string, numeric types
2. Enums
3. Type
4. System.Object : 이 경우 System.Object를 상속받는 클래스가 아닌 System.Object 자체만 가능하다.
5. 1차원 배열 ( 위 1~4번 타입들에 대한 )
위처럼 제한되는 이유는 간단하다.
attribute는 유형(type)에 대한 메타데이터이며, 유형(type)은 컴파일 타임에 정의된다.
이것이 바로 attribute 생성자에 인수로 전달된 모든 값이 컴파일 당시에 attribute에 포함되어야 하는 이유다.
스택오버 플로우에서 보면 다음과 같은 글을 확인할 수 있다.
The problem isn't passing a List<string> to a constructor in general - the problem is that you're trying to use it for an attribute. You basically can't do that, because it's not a compile-time constant.
즉, 런타임 중 결정되는 자료형은 attribute parameter로 사용할 수 없다.
'TIL (Today I Learned) > C#' 카테고리의 다른 글
[C#] Tuples, ValueTuples (2) | 2024.04.24 |
---|---|
[C#] using, IDisposable (1) | 2024.04.18 |
댓글