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을 사용해 앱을 만들때