8. reflection 과 attributes
.NET의 컴파일(빌드) 결과는 대게 DLL이나 EXE 형태의 파일이 될 수 있는데 이를 통틀어 Assembly라고도 합니다. 그리고 .NET의 Assembly는 크게 아래 4가지 부분으로 구성됩니다.
Assembly metadata와 manifest | Assembly의 File버전이나 참조된 Assembly등등을 나타냅니다. |
Type metadata | Member와 같은 Type의 정보를 나타냅니다. |
IL code | Methods나 속성, 생성자등을 구현합니다. |
Embedded resources | Images와 같은 별개의 리소스자원을 나타냅니다. |
Metadata는 Code에서 구현된 Member나 Type 등의 정보를 토대로 자동으로 생성되거나 attributes를 사용하여 Code에 적용됩니다. 여기서 Attributes는 Assembly나
[assembly: AssemblyTitle("AssemblyTitle")]
[assembly: AssemblyDescription("AssemblyDescription")]
Type, Member 등 다양한 범위에서 다음과 같은 식으로 적용될 수 있습니다.
using System;
using System.Reflection;
namespace myapp
{
class Program
{
[Obsolete("진입점")]
static void Main(string[] args)
{
}
}
}
위와 같은 Attribute기반의 Programming 방식은 routing, security, caching 등을 목적으로 하는 ASP.NET Core 등의 App모델에서도 적극적으로 사용되고 있습니다.
● Assembly의 Versioning
.NET에서의 Version번호는 선택적으로 추가되는 2개와 함께 총 다섯 가지의 숫자 조합으로 결정될 수 있습니다.
- Major : 전체적인 주요 변화
- Minor : 새로운 기능의 추가 및 버그 수정
- Patch : 버그 수정
- Prerelease
- Build number
Sementic Versorning은 아래 규칙을 참고합니다.
Semantic Versioning 2.0.0 | Semantic Versioning (semver.org)
Project에서 사용 중인 NuGet Package를 업데이트하는 경우 더 높은 버전의 Minor 혹은 Patch로만 업데이트를 수행할 수 있습니다. 이는 Major의 버전업이 기존 Code의 변경을 요구하는 경우 Code의 수정이 불가피해지는데 이러한 문제 발생을 최대한 피하고자 하기 위함입니다. 예를 들어 Newtonsoft.Json의 경우라면 아래 명령으로 이를 수행할 수 있습니다.
Update-Package Newtonsoft. Json -ToHighestMinor or Update-Package Newtonsoft.Json -ToHighestPatch. |
● Assembly의 metadata 읽어오기
Assembly의 Metadata를 통해서는 해당 Assembly의 각종 속성 및 Type정보 등을 확인할 수 있고 이를 Code를 통해 읽어로 려면 System.Reflection을 사용해야 합니다.
using System;
using System.Reflection;
namespace myapp
{
class Program
{
static void Main(string[] args)
{
Assembly? assembly = Assembly.GetEntryAssembly();
if (assembly is null)
return;
Console.WriteLine($"FullName : {assembly.FullName}");
//FullName : myapp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Console.WriteLine($"Location : {assembly.Location}");
IEnumerable<Attribute> attributes = assembly.GetCustomAttributes();
Console.WriteLine($"Attributes : ");
foreach (Attribute a in attributes)
Console.WriteLine($" {a.GetType()}");
}
}
}
위 예제를 실행하면 현재 Console App의 Assembly정보를 살펴볼 수 있는데 특히 FullName은 아래 정보를 조합하여 사용함으로써 Assembly를 식별하기 위한 유일한 값으로 취급될 수 있습니다.
- Name
- Version
- Culture
- Public key token
Assembly를 생성하면 아래와 같이 자동적으로 decorating 되는 Attribute가 존재하는데
[assembly: AssemblyCompany("myapp")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("myapp")]
[assembly: AssemblyTitle("myapp")]
[assembly: AssemblyVersion("1.0.0.0")]
필요한 경우 위의 정보를 Code를 통해서 확인할 수도 있습니다. 이때 Version에 관한 것이면 AssemblyInformationalVersionAttribute를, Company에 관한 것이면 AssemblyCompanyAttribute와 같이 속성 이름과 일치되는 Class를 사용합니다.
Assembly? assembly = Assembly.GetEntryAssembly();
if (assembly is null)
return;
AssemblyInformationalVersionAttribute? version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Console.WriteLine($"Version: {version?.InformationalVersion}");
AssemblyCompanyAttribute? company = assembly.GetCustomAttribute<AssemblyCompanyAttribute>();
Console.WriteLine($"Company: {company?.Company}");
별도로 해당 정보를 설정하지 않는다면 Version은 기본적으로 1.0.0이 되고 Company는 Assembly의 이름이 됩니다. 물론 이들 정보는 임의로 설정이 가능한데 .NET Framework에서는 사용하던 방법은 소스코드에서 직접 해당 Attribute를 지정하던 것이었지만
[assembly: AssemblyCompany("cliel")]
[assembly: AssemblyInformationalVersion("1.1.1")]
.NET의 Roslyn 컴파일러는 이들 정보를 자동으로 생성하기 때문에 같은 방법을 더 이상 사용할 수 없고 대신 해당 정보를 Project파일(csproj)에 직접 설정해야 합니다.
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishTrimmed>true</PublishTrimmed>
<Version>1.1.1</Version>
<Company>cliel</Company>
</PropertyGroup>
● 사용자 Attribute 생성하기
Attribute Class를 상속하면 직접 필요한 Attribute를 정의해 사용할 수 있습니다. 예를 들어 Class나 Method에 작업자의 이름과 작업일자를 통해 작업 이력을 남겨야 하는 Attribute를 생성해야 한다면 다음과 같이 Class를 정의할 수 있습니다.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class WorkerAttribute : Attribute
{
public string WorkerName { get; set; }
public DateTime WorkDate { get; set; }
public WorkerAttribute(string workerName, string workDate)
{
WorkerName = workerName;
WorkDate = DateTime.Parse(workDate);
}
}
이때 Attribute의 이름은 Worker가 되며 아래와 같이 Class나 Method에 이름과 날짜 값을 전달하여 Attribute를 작성합니다.
[Worker("홍길동", "2022-01-01")]
[Worker("홍길석", "2022-02-02")]
class Program
{
[Worker("홍길순", "2022-03-03")]
static void Main(string[] args)
{
}
}
그리고 이렇게 생성된 Attribute는 Type의 Member를 확인한 뒤 각 Member에서 GetCustomAttributes()메서드를 통해 지정된 값을 읽을 수 있습니다.
[Worker("홍길동", "2022-01-01")]
[Worker("홍길석", "2022-02-02")]
class Program
{
[Worker("홍길순", "2022-03-03")]
static void Main(string[] args)
{
Assembly? assembly = Assembly.GetEntryAssembly();
if (assembly is null)
return;
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
MemberInfo[] members = type.GetMembers();
foreach (MemberInfo member in members)
{
Console.WriteLine($"{member.Name} -> 작업이력");
IEnumerable<WorkerAttribute> workers = member.GetCustomAttributes<WorkerAttribute>();
foreach (WorkerAttribute worker in workers)
{
Console.WriteLine($"작업자 : {worker.WorkerName}({worker.WorkDate.ToShortDateString()})");
}
}
}
//SomeMethod -> 작업이력
//작업자 : 홍길순(2022-03-03)
}
}
● Reflection 활용
상기 예제들은 그저 Reflection을 통해 Metadata를 읽어내는 것이 전부입니다. 이외 Reflection을 통해서는 기타 아래 작업들이 가능하며 자세한 사항은 같이 추가된 URL을 참고하시기 바랍니다.
- 참조되지 않은 Assembly의 동적 Load : How to use and debug assembly unloadability in .NET Core | Microsoft Docs
- 동적 Code실행 : MethodBase.Invoke Method (System.Reflection) | Microsoft Docs
- 새로운 Code와 Assembly의 동적 생성 : AssemblyBuilder Class (System.Reflection.Emit) | Microsoft Docs
'.NET' 카테고리의 다른 글
[.NET] C#과 NET의 프로젝트 유형 - 1. App Model (0) | 2022.07.20 |
---|---|
[.NET] 닷넷 Type 사용하기 - 8. image 다루기 (0) | 2022.06.26 |
[.NET] 닷넷 Type 사용하기 - 6. 네트워크 리소스 활용 (0) | 2022.06.26 |
[.NET] 닷넷 Type 사용하기 - 5. index와 range 그리고 Span (0) | 2022.06.26 |
[.NET] 닷넷 Type 사용하기 - 4. Collection 사용 (0) | 2022.06.26 |