[.NET] 닷넷 Type 사용하기 - 7. reflection 과 attributes
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)
Semantic Versioning 2.0.0
Semantic Versioning spec and website
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
How to use and debug assembly unloadability in .NET Core
Learn how to use collectible AssemblyLoadContext for loading and unloading managed assemblies and how to debug issues preventing the unloading success.
docs.microsoft.com
- 동적 Code실행 : MethodBase.Invoke Method (System.Reflection) | Microsoft Docs
MethodBase.Invoke Method (System.Reflection)
Invokes the method or constructor reflected by this MethodInfo instance.
docs.microsoft.com
- 새로운 Code와 Assembly의 동적 생성 : AssemblyBuilder Class (System.Reflection.Emit) | Microsoft Docs
AssemblyBuilder Class (System.Reflection.Emit)
Defines and represents a dynamic assembly.
docs.microsoft.com