개발꿈나무
[C# 교과서] 25. C# 활용(19) - 특성과 리플렉션 본문
특성과 리플렉션
특성(attribute)은 프로그램에서 형식, 멤버, 다른 엔터티에 대한 추가 선언 정보를 저장한다.
특성
C#에서 특성은 데코레이트(decorator)와 애너테이션(annotation) 성격을 띈다. 간단히 말해 프로그램 코드에 설명을 추가로 붙이는 것이다.
- 특성은 프로그램에 메타데이터(metdata)를 추가한다.
- 데코레이터와 애너테이션 성격을 지닌다.
- 꾸밈자(decorate, describe, declarative) 역할을 한다.
- 여러 구성 요소에 추가 정보를 제공한다.
특성은 C# 구성 요소 앞에 대괄호([])로 표시한다.
[Obsolete]
public class OldClass{}
- 닷넷에 내장된 특성: 닷넷에 내장된 특성으로 대괄호 기호를 멤버 앞에 붙여 사용
- 사용자 지정 특성: 사용자가 새로운 특성을 직접 만드는 것으로 Attribute 클래스를 상속하는 클래스로 사용자 지정 특성을 생성
Obsolete 특성 사용하기
Obsolete 특성은 C# 9.0 이상의 버전에서만 사용 가능하다.
using System;
public class ObsoleteDemo
{
static void Main()
{
void OldMember() => Console.WriteLine("Old Method");
OldMember();
[Obsolete] void NewMember() => Console.WriteLine("New Method");
NewMember();
[Obsolete("Using New Member Method")] //Obsolete 특성에 경고 메시지 지정 가능
void NewMember2() => Console.WriteLine("New Method2");
[Obsolete("Using New Member Method", true)] //두번째 매개변수를 true로 지정 -> 실행 오류
void NewMethod3() => Console.WriteLine("New Method3");
}
}
OldMember(), NewMember() 모두 실행에 에러는 나지 않지만,
비주얼 스튜디오에서는 Obsolete 특성이 적용된 메서드를 호출하면 컴파일러 단에서 경고 메시지가 표시된다.
Obsolete 특성에 경고 메시지를 지정할 수 있으며, 두번째 매개변수 값을 true로 지정하면 실행 에러가 난다.
[Conditional] 특성 사용하기
Conditional 특성을 사용하면 특정 기호(symbol)에 따라 실행 여부를 결정할 수 있다.
#define RELEASE //[2][1] 전처리기 지시문으로 RELEASE 기호 정의
using System;
using System.Diagnostics;
public class ConditionalDemo
{
static void Main()
{
DebugMethod();
ReleaseMethod();
}
[Conditional("DEBUG")] //[1] DEBUG 기호(심볼)을 가지는 경우에 실행
static void DebugMethod() => Console.WriteLine("디버그 환경에서만 표시");
//[2][2] RELEASE 기호가 있는 경우에 실행
[Conditional("RELEASE")] static void ReleaseMethod()
=> Console.WriteLine("릴리스 환경에서만 표시");
}
비주얼 스튜디오의 도구 모음은 Debug와 Release를 구분지을 수 있는 드롭다운 리스트를 제공하는데
이를 사용하여 프로그램에 DEBUG와 RELEASE 기호를 제공할 수 있다.
특성을 사용하여 메서드 호출 정보 얻기
using System.Runtime.CompilerServices;
using static System.Console;
class CallerInformation
{
static void Main()
{
TraceMessage("실행");
}
public static void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
WriteLine("실행 내용: " + message);
WriteLine("멤버 이름: " + memberName);
WriteLine("소스 경로: " + sourceFilePath);
WriteLine("실행 라인: " + sourceLineNumber);
}
}
/*
실행 내용: 실행
멤버 이름: Main
소스 경로: C;\C#\CallerInformation\CallerInformation\CallerInformatin.cs
실행 라인: 8
*/
메서드의 매개변수 앞에서 특성을 사용하여 메서드를 호출한 호출자 정보를 얻을 수 있다.
사용자 지정 특성 만들기
클래스, 메서드 등에 대괄호를 붙여 사용할 수 있는 특성을 직접 원하는 이름으로 만들 수 있다.
using System;
// [1] Attribute 클래스를 상속하여 사용자 지정 특성 만들기
public class SampleAttribute : Attribute
{
public SampleAttribute() => Console.WriteLine("사용자 지정 특성 사용됨");
}
[Sample]
public class CustomAttributeTest { }
class AttributePractice
{
static void Main()
{
// [2] CustomAttributeTest 클래스에 적용된 특성들 가져오기
Attribute.GetCustomAttributes(typeof(CustomAttributeTest));
}
}
// 사용자 지정 특성 사용됨
[1] Attribute 클래스를 상속하여 SampleAttribute 이름의 특성 생성
사용자 지정 특성은 ~Attribute로 끝나고, 이를 줄여[Sample] 형태로 표현 가능
[2] CustomAttributeTest에 적용된 특성 목록을 가져오면서 SampleAttribute 클래스의 생성자를 호출하여 "사용자 지정 특성 사용됨" 문자열 출력
매개변수가 있는 사용자 지정 특성 만들기
using System;
// [1] AttributeUsage 특성을 사용하여 특성에 대한 제약 조건 등 설정
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class NickNameAttribute : Attribute
{
public string Name { get; set; }
public NickNameAttribute(string name) { Name = name; }
}
// [2] AllowMultiple에 의해서 여러 번 설정 가능
[NickName("길벗")]
[NickName("RedPlus")]
class NickNameAttributeTest
{
static void Main() => ShowMetaData();
static void ShowMetaData()
{
// 모든 커스텀 어트리뷰트 가져오기
Attribute[] attrs =
Attribute.GetCustomAttributes(typeof(NickNameAttributeTest));
foreach (var attr in attrs)
{
// [A] is 연산자를 사용하여 커스텀 어트리뷰트의 Name 속성 출력
if (attr is NickNameAttribute)
{
NickNameAttribute ais = (NickNameAttribute)attr;
Console.WriteLine("{0}", ais.Name);
}
// [B] as 연산자를 사용하여 커스텀 어트리뷰트의 Name 속성 출력
NickNameAttribute aas = attr as NickNameAttribute;
if (aas != null)
{
Console.WriteLine("{0}", aas.Name);
}
}
}
}
리플렉션
리플렉션(reflection)은 동적으로 특정 어셈블리 또는 형식에 대한 메타데이터를 Type 개체로 반환하는 것을 의미한다. 리클렉션을 사용하면 특성 정보를 얻거나 동적으로 특정 형식을 로드하여 사용할 수 있다.
Type과 Assembly 클래스
using System;
using System.Reflection;
namespace ReflectionGetMembers
{
class Test
{
public static void TestMethod() { }
}
class ReflectionGetMembers
{
static void Main()
{
// Test 클래스에 대한 Type 개체 가져오기
Type t = typeof(Test);
// 원하는 멤버를 조건에 따라 가져오기
MemberInfo[] members =
t.GetMembers(BindingFlags.Static | BindingFlags.Public);
// 멤버 출력
foreach (var member in members)
{
Console.WriteLine("{0}", member.Name); //TestMethod
}
}
}
}
리플렉션을 사용하여 Test 클래스의 정적 멤버 리스트를 얻은 후 멤버 이름을 출력한다.
리플렉션을 사용하면 특정 클래스의 전체 멤버 리스트 또는 특정 조건에 맞는 멤버를 얻을 수 있다.
Type 클래스로 클래스의 멤버 호출하기
using System;
using System.Reflection;
namespace ReflectionGetMethod
{
public class MemberClass
{
public string Name { get; set; } = "길벗출판사";
public string GetName()
{
return Name + ", " + DateTime.Now.ToShortTimeString();
}
}
class ReflectionGetMethod
{
static void Main()
{
//[1] 리플렉션 기능으로 특정 클래스의 멤버를 동적으로 호출(Invoke)
MemberClass m = new MemberClass();
Type t = m.GetType();
//[a] 속성 읽어오기 및 속성 호출
PropertyInfo pi = t.GetProperty("Name"); // Name 속성
Console.WriteLine("속성 호출: {0}", pi.GetValue(m)); //길벗출판사
//[b] 메서드 읽어오기 및 메서드 호출
MethodInfo mi = t.GetMethod("GetName"); // GetName 메서드
Console.WriteLine("메서드 호출: {0}", mi.Invoke(m, null)); //길벗출판사, 오전 1:29
//[2] 참고: C# 4.0 이상에서는 dynamic 개체로 쉽게 멤버를 동적으로 호출
dynamic d = new MemberClass(); // dynamic 키워드로 동적 개체 생성
Console.WriteLine("속성 호출: {0}", d.Name); // 속성 호출: 길벗출판사
Console.WriteLine("메서드 호출: {0}", d.GetName()); // 메서드 호출: 길벗출판사, 오전 1:29
}
}
}
특정 속성에 적용된 특성 읽어오기
using System;
using System.Reflection;
namespace ReflectionGetProperty
{
class Person
{
[Obsolete] public string Name { get; set; }
}
class ReflectionGetProperty
{
static void Main()
{
// Name 속성의 정보 얻기
PropertyInfo pi = typeof(Person).GetProperty("Name");
// Name 속성에 적용된 특성 읽어오기
object[] attributes = pi.GetCustomAttributes(false);
foreach (var attr in attributes)
{
// 특성의 이름들 출력
Console.WriteLine("{0}", attr.GetType().Name);
}
}
}
}
//ObsolteAttribute
<Reference>
'C# 기초' 카테고리의 다른 글
[C# 교과서] 27. C# 확장 기능(2) - 동적 형식 (0) | 2022.01.25 |
---|---|
[C# 교과서] 26. C# 확장 기능(1) - 확장 메서드 만들기 (0) | 2022.01.25 |
[C# 교과서] 24. C# 활용(18) - 인터페이스 (0) | 2022.01.21 |
[C# 교과서] 23. C# 활용(17) - 메서드 오버라이드 (0) | 2022.01.20 |
[C# 교과서] 22. C# 활용(16) - 상속으로 클래스 확장하기 (0) | 2022.01.20 |