Search
Duplicate

Reflection & Attribute

작성일
2022/06/03 16:11
분류
C#

Reflection

약한 동적 코드 접근/제어 기능

Type

class / struct / record / array / interface / enum / delegate / <T> / …
등 모든 객체의 타입에 관한 메타데이터를 담은 class
str.GetType()
Type.GetType(”System.String”)
typeof(string)
//💡Type도 결국 class다. 다음과 같이 실수하지 않도록 주의 var t = typeof(string); Debug.Log(t); // System.String Debug.Log(t.GetType()); // System.RuntimeType
C#
복사

관련 메소드

메소드
반환
내용
GetConstructors()
ConstructorInfo[]
생성자 목록
GetInterfaces()
Type[]
인터페이스 목록
GetEvents()
EventInfo[]
이벤트 목록
GetMethods()
MethodInfo[]
메서드 목록
GetMembers()
MemberInfo[]
멤버 목록
GetFields()
FieldInfo[]
필드 목록
GetProperties()
PropertyInfo[]
프로퍼티 목록

BindingFlags

접근 제한자
작동 방식
인자
DeclaredOnly
CreateInstance
ExactBinding
FlattenHierarchy
GetField
OptionalParamBinding
IgnoreCase
SetField
IgnoreReturn
GetProperty
Instance
SetProperty
NonPublic
InvokeMethod
Public (기본값)
PutDispProperty
Static
PutRefDispProperty
//다음과 같이 사용 var t = typeof(string); var flags = BindingFlags.Public | BindingFlags.Static; foreach (var item in t.GetMethods(flags)) { Debug.Log(item.Name); }
C#
복사

Activator

런타임에 타입 정보를 모르고도 인스턴스를 생성 가능
object o = Activator.CreateInstance(type); object o = Activator.CreateInstance<string>(type); // Generic 버전도 존재는 한다
C#
복사

GetValue, SetValue, Invoke

public class Bird { private string Name { get; set; } private int _id = 1; private void Fly() => Debug.Log($"{Name} is flying ({_id})"); } ------------------------- var type = typeof(Bird); var obj = Activator.CreateInstance(type); var flags = BindingFlags.NonPublic | BindingFlags.Instance; var nameProperty = type.GetProperty("Name", flags); var idField = type.GetField("_id", flags); var flyMethod = type.GetMethod("Fly", flags); nameProperty.SetValue(obj, "Duck"); var prevId = (int)idField.GetValue(obj); var newId = prevId + 1; idField.SetValue(obj, (object)newId); flyMethod.Invoke(obj, null); // Duck is flying (2)
C#
복사

nameof

public 맴버 / 메소드 등의 이름을 인스턴스 없이 가져올때 유용
하드코딩을 줄일 수 있다
public class Car { public string Name { get; set; } public void Move() => Debug.Log($"{Name} is moving"); } ------------------------- var type = typeof(Car); object obj = Activator.CreateInstance(type); var flags = BindingFlags.Public | BindingFlags.Instance; var nameProperty = type.GetProperty(nameof(Car.Name), flags); var moveMethod = type.GetMethod(nameof(Car.Move), flags); nameProperty.SetValue(obj, "Cybertruck"); moveMethod.Invoke(obj, null); //Cybertruck is moving
C#
복사

언제 사용할까?

런타임 코드 생성이 필요할때
Json 파서
유저 Mod 구현
내장 클래스를 건드릴때
GC 방지를 위한 StringBuilder 조작
Unity 내장 클래스 수정 등
다른 언어와 호환이 필요할때
Python 에서 작성한 코드를 번역해 실행도 가능
이 경우 IL 코드에 대한 높은 이해도가 필요
Unity PropertyDrawer 구현할때
UnityEngine.Editor 를 상속받으면 UnityEngine.Object 를 상속받은 target 을 가져와주지만 PropertyDrawer 는 [SerializedField] 로 선언된 필드의 SerializedProperty 정보밖에 가져오지 못한다. 이때 Reflection을 이용하면 PropertyDrawer 에서도 대상 object를 수정 가능하다

Attribute

class / struct / record / array / interface / enum / delegate / <T> / …
등 모든 객체의 코드 자체에 추가적인 메타데이터 정보를 추가하기 위한 문법
Attribute에 들어가는 인자는 컴파일 타임에 정적이어야 한다.

주요 Attribute

[Deprecated]
[Obsolete]
[Serializable]
[SerializeField]
[DllImport(”xxx.dll”)]

Custom Attribute 생성과 이용

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] class ButtonAttribute : Attribute { public string Name { get; set; } public ButtonAttribute(string name) => Name = name; } ------------------------- [Button(nameof(Rotate))] public bool RotateButton; public void Rotate() { // Do something. } ------------------------- [CustomPropertyDrawer(typeof(ButtonAttribute))] public class ButtonDrawer : PropertyDrawer { public override void OnGUI(Rect rect, SerializedProperty prop, GUIContent label) { var methodName = (attribute as ButtonAttribute).Name; var target = prop.serializedObject.targetObject; var type = target.GetType(); var method = type.GetMethod(methodName); if (GUI.Button(rect, method.Name)) { method.Invoke(target, null); } } }
C#
복사

[field: xxx]

[AttributeUsage(AttributeTargets.Field)] 로만 선언된 경우, 프로퍼티에서 attribute 사용이 불가능하다
[SerializeField] 가 대표적인 예
이때, Property 의 Attribute 가 아닌 backing field한테 Attribute 를 붙여주고 싶을 경우 다음과 같이 사용
[field: SerializeField] public string name { get; private set; }
// backing field 를 직접 구현하는 경우 다음과 같은 boilerplate가 반복해서 사용됨 [SerializeField] private string _name; public string Name { get => _name; private set => _name = value; } // 자동 구현 프로퍼티와 [field: xxx] 문법을 이용하면 한줄로 간편하게 구현 가능 [field: SerializeField] public string Name { get; private set; }
C#
복사

언제 사용할까?

직렬화 / 역직렬화 메타 정보를 추가할때
Json 파서
Unity Inspector에 노출되는 Field를 꾸밀때
내가 만든 API를 [Deprecated], [Obsolete] 로 표시하고 싶을 때
외부 dll을 사용해 앱을 만들때