리플렉션,애트리뷰트
< 리플렉션(Reflection)이란? >
리플렉션은 프로그램이 실행 중에 자기 자신의 구조를 들여다보고, 조작하는 기능이다. 마치 바코드 스캐너가 상품의 정보를 읽는 것과 같다.
Object.GetType() 이 메서드를 사용하면, 객체의 타입 정보를 얻을 수 있다. 과일 상자에 있는 과일이 어떤 종류인지 확인하는 것과 비슷하다.
var number = 5;
Type type = number.GetType();
Console.WriteLine(type); // 출력: System.Int32
< Type 클래스>
Type 클래스는 C#의 리플렉션 기능과 밀접한 관련이 있는데, 이 클래스를 사용하면 타입에 대한 상세한 정보를 조회하고 조작할 수 있다. 타입은 클래스, 인터페이스, 열거형, 구조체 등과 같은 프로그래밍 요소를 의미한다.
아래는 StringBuilder라는 클래스의 인스턴스를 리플렉션을 통해 동적으로 생성하는 코드다.
Type myType = typeof(StringBuilder);
object myInstance = Activator.CreateInstance(myType);
Type myType = typeof(StringBuilder);
typeof 키워드를 사용하여 StringBuilder 클래스의 Type 객체를 가져온다. StringBuilder는 문자열을 조작할 때 사용되는 C#의 기본 클래스 중 하나다.
myType 변수에는 이제 StringBuilder 클래스에 대한 메타데이터 정보가 담긴 Type 객체가 저장된다.
Type은 C#이 제공하는 클래스와 메서드가 있고, 그걸 typeof를 사용하면, Type안에 있는 기능을 가져올 수 있다.
object myInstance = Activator.CreateInstance(myType);
Activator.CreateInstance 메서드를 사용하여, 앞서 가져온 Type 객체(myType)를 기반으로 새 인스턴스를 생성한다.
이렇게 생성된 인스턴스는 object 형식의 myInstance 변수에 저장된다. 이 경우, myInstance는 StringBuilder 클래스의 새 인스턴스를 참조하게 된다.
주목할 점은, 일반적으로 new StringBuilder()와 같은 구문을 사용하여 인스턴스를 생성하는 대신, 리플렉션을 사용하여 동적으로 인스턴스를 생성한다는 것이다. 이 기법은 특정 타입이 런타임에 결정될 때 유용하며, 런타임에 객체를 생성하고 조작할 수 있다.
스마트폰을 사려고 하는데, 일반적으로 직원에게 "아이폰13 모델을 보고싶네요"라고 할 것이다. 이때, 직원은 아이폰13을 가져와서 고객에게 주는데, 이 상황이 일반적으로 객체를 생성하는 방법과 비슷하다. 즉, new iPhone13()처럼 코드로 작성해서 바로 객체를 만드는 것이다.
하지만, 이번에는 "아이폰"이라는 것에 대한 정보가 적힌 카드를 가지고 있다. 그런데 이 카드에는 모델 번호가 적혀있지 않다. 그래서 가게 직원에게 이 카드를 보여주며 "이 정보를 가지고 있는 스마트폰을 하나 주세요"라고 말한다. 직원은 카드를 보고, 해당 정보에 맞는 스마트폰을 찾아서 준다.
이 상황이 바로 Activator.CreateInstance(myType)를 사용하는 상황과 비슷하다. 이 메서드를 통해, 우리가 가지고 있는 Type 객체(정보가 적힌 카드)를 사용하여 새로운 객체(스마트폰)를 생성하는 것이다.
MethodInfo appendStringMethod = myType.GetMethod("Append");
appendStringMethod.Invoke(myInstance, new object[] { "Hello, Reflection!" });
이 코드는 StringBuilder 클래스의 Append 메서드를 동적으로 찾아 호출하는 것을 보여준다. 이렇게 리플렉션을 사용하면, 컴파일 시간이 아닌 런타임에 객체와 해당 타입의 멤버들을 다룰 수 있다.
예를 들자면 로봇이 명령에 따라 다양한 종류의 샌드위치를 만들 수 있는 것과 비슷하다.
MethodInfo appendStringMethod = myType.GetMethod("Append");
이 코드는 마치 로봇에게 말하는 것과 비슷하다. "로봇아, 너가 샌드위치를 만드는 데 사용할 수 있는 'Append'라는 기능(메서드)이 있는지 확인해줘." 라고 말하는 것이다. 로봇은 내부 메뉴얼을 확인하고 'Append'라는 기능을 찾아준다. 여기서 myType은 로봇의 메뉴얼이고, appendStringMethod는 'Append'라는 기능에 대한 정보를 가지고 있게 되는 것이다.
appendStringMethod.Invoke(myInstance, new object[] { "Hello, Reflection!" });
이제 로봇에게 샌드위치를 만들도록 명령할 시간이다. 이 코드는 마치 "로봇아, 방금 찾아준 'Append' 기능을 사용해서, 'Hello, Reflection!' 이라는 재료를 넣어 샌드위치를 만들어줘." 라고 말하는 것과 비슷하다. 여기서 myInstance는 로봇이 만들고 있는 샌드위치(즉, StringBuilder 객체)이고, "Hello, Reflection!"은 샌드위치에 넣을 재료다.
리플렉션을 사용하면, 컴파일 시간에 정해진 것이 아니라 실행 중에 어떤 기능을 사용할지, 어떤 객체에 적용할지 결정할 수 있다. 이는 마치 로봇에게 동적으로 다양한 샌드위치를 만들도록 지시하는 것과 비슷하다.
< 애트리뷰트(Attribute)란? >
애트리뷰트는 코드에 추가 정보를 제공하는 데 사용된다. 상품에 붙어있는 라벨과 비슷하다. 예를 들어, 어떤 메서드가 작업을 수행하는 데 오랜 시간이 걸린다고 알리고 싶을 때 사용할 수 있다.
[애트리뷰트 작성하기]
애트리뷰트를 사용하려면 대괄호([]) 안에 애트리뷰트 이름을 적고, 해당 코드 요소 앞에 붙인다. 라벨을 만들어 상품에 붙이는 것처럼, 애트리뷰트도 만들어서 코드에 붙일 수 있는 것이다.
[AttributeUsage(AttributeTargets.Method)]
public class LongRunningAttribute : Attribute
{
public string Message { get; }
public LongRunningAttribute(string message)
{
Message = message;
}
}
[애트리뷰트 사용하기]
애트리뷰트를 사용하려면 대괄호([]) 안에 애트리뷰트 이름을 적고, 해당 코드 요소 앞에 붙인다.
[LongRunning("This method takes a long time.")]
public void TimeConsumingMethod()
{
// ...
}
< 실습 >
Visual Studio를 실행해서. "새 프로젝트 만들기"를 클릭 후, "C# Console App"을 선택하고, "다음"을 클릭한다.
프로젝트 이름을 입력하고, "만들기"를 클릭한다.
Program.cs 파일을 아래와 같이 수정해보자.
using System;
using System.Reflection;
using System.Text;
namespace ReflectionAndAttributesDemo
{
[AttributeUsage(AttributeTargets.Method)]
public class LongRunningAttribute : Attribute
{
public string Message { get; }
public LongRunningAttribute(string message)
{
Message = message;
}
}
public class Program
{
[LongRunning("This method takes a long time.")]
public static void TimeConsumingMethod()
{
}
public static void Main(string[] args)
{
// Reflection
var myNumber = 5;
Type myType = myNumber.GetType();
Console.WriteLine($"Type of myNumber: {myType}");
Type stringBuilderType = typeof(StringBuilder);
object myStringBuilder = Activator.CreateInstance(stringBuilderType);
Console.WriteLine($"Type of myStringBuilder: {myStringBuilder.GetType()}");
// Using Attribute
MethodInfo methodInfo = typeof(Program).GetMethod("TimeConsumingMethod");
var attribute = (LongRunningAttribute)methodInfo.GetCustomAttribute(typeof(LongRunningAttribute));
Console.WriteLine($"Attribute message: {attribute.Message}");
}
}
}
여기서 아래의 코드부터 상세히 보자.
[AttributeUsage(AttributeTargets.Method)]
public class LongRunningAttribute : Attribute
{
public string Message { get; }
public LongRunningAttribute(string message)
{
Message = message;
}
}
[AttributeUsage(AttributeTargets.Method)] : 이 애트리뷰트가 메서드에만 적용될 수 있도록 제한.
public string Message { get; } : 읽기 전용 속성으로, 애트리뷰트가 전달하는 메시지를 저장한다.
public LongRunningAttribute(string message) : 생성자로, 애트리뷰트를 생성할 때 메시지를 전달받아 Message 속성에 저장한다.
[LongRunning("오랜 시간 걸립니다.")]
public static void TimeConsumingMethod()
{
}
[LongRunning("오랜 시간 걸립니다.")] : LongRunningAttribute 애트리뷰트를 메서드에 적용하고, 메시지를 전달한다.
public static void Main(string[] args)
{
// Reflection
var myNumber = 5;
Type myType = myNumber.GetType();
Console.WriteLine($"Type of myNumber: {myType}");
Type stringBuilderType = typeof(StringBuilder);
object myStringBuilder = Activator.CreateInstance(stringBuilderType);
Console.WriteLine($"Type of myStringBuilder: {myStringBuilder.GetType()}");
// Using Attribute
MethodInfo methodInfo = typeof(Program).GetMethod("TimeConsumingMethod");
var attribute = (LongRunningAttribute)methodInfo.GetCustomAttribute(typeof(LongRunningAttribute));
Console.WriteLine($"Attribute message: {attribute.Message}");
}
Ctrl + F5를 눌러 프로그램을 실행한다.
.