C#

리플렉션과 어트리뷰트

khkg12 2024. 1. 2. 15:42

리플렉션이란?

클래스의 메타데이터(변수, 함수, 프로퍼티 등)을 런타임에서 가져와 사용할 수 있는 기능이다. 리플렉션은 탐색 과정을 거치기 때문에 연산이 많이 들기 때문에 이 점을 유의하며 사용해야 한다. 

 

어트리뷰트란?

속성을 부여하는 것으로 자주사용되는 [SerializeField] 어트리뷰트 또한 직렬화라는 속성을 어트리뷰트가 붙여진 변수에 부여하는 것이다.

 

예시 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using System;

// 객체 : 속성과 기능을 가진 것, 멤버는 속성과 기능 전부를 뜻함
public class Monster
{
    public string name;
    private int hp;
    private int maxhp;
    private float atk;

    public int Hp
    {
        get => hp;
        set { hp = value; }
    }

    public void Attack()
    {
        Debug.Log(name + $"이 공격");
    }

    public void Hit()
    {
        Debug.Log("몬스터 맞았다");
        Hp -= 10;
    }    
}

public class Npc
{
    public string name;
    public void Attack()
    {
        Debug.Log(name + "은 공격하지 않는다.");
    }

    public void Hit()
    {
        Debug.Log("맞았다");        
    }
}

public class GameManager : MonoBehaviour
{
    // 리플렉션
    // Reflection : 메타적인 데이터를 가져오는 문법
    public Box box;
    Monster monsterA;
    Monster monsterB;
    
    void Start()
    {        
        monsterA = new Monster();
        monsterB = new Monster();        

        monsterA.Hp = 100;
        monsterA.name = "슬라임";
        monsterB.Hp = 300;                

        Type type = monsterA.GetType();
        type = monsterB.GetType();
        type = typeof(Monster);
        // 셋 다 몬스터라는 클래스에 대한 정보를 가져오는 것으로, 셋 다 동일

        Debug.Log(type.Name);
        MemberInfo[] minfos = type.GetMembers(); // private은 안가져옴
        minfos = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // 퍼블릭인 놈과 논퍼블릭, 인스턴스 멤버인 애들을 다가져옴
        // 위처럼 가져올때(바인드를 엮어줄때) public만 쓰면 안됨. 그놈이 인스턴스 영역인지 스태틱 영역인지도 알려주어야함.
        // object가 가지고 있는 GetType, .ctor같은 것도 다 나옴.

        FieldInfo hpFieldInfo = type.GetField("hp", BindingFlags.NonPublic | BindingFlags.Instance);
        hpFieldInfo.SetValue(monsterB, 50); // 값을 누구의 것을 변경할지 정해주고, 어떤값으로 바꿀껀지 넣어줌
        Debug.Log(hpFieldInfo.GetValue(monsterA)); // monsterA 객체의 hp를 가져옴
        Debug.Log(hpFieldInfo.GetValue(monsterB)); // monsterB 객체의 hp를 가져옴

        MethodInfo attackMethodInfo = type.GetMethod("Attack");
        
        attackMethodInfo.Invoke(monsterA, null);


        //foreach (MemberInfo minfo in minfos)
        //{
        //    Debug.Log(minfo.Name);
        //}

        //Debug.Log(monsterA.GetType()); // 위 아래 동일함
        //Debug.Log(monsterB.GetType()); 
        // monsterA.GetType().Hp; <- 불가능, 타입에 대한 정보를 가져오는것이지 그 자체가 아님, 몬스터 객체 그자체가 아닌, 그정보를 담고있는 것일 뿐

        Npc npc = new Npc();        
        npc.name = "상인";

        string methodName = "Hit";

        Attack(monsterA, methodName); // Invoke나 StartCourtine을 쓸 때의 과정과 동일함.
        Attack(npc, methodName);         
        Attack(box, methodName);
    }        

    public void Attack(object obj, string methodName)
    {
        Type type = obj.GetType();
        MethodInfo attackMethodInfo = type.GetMethod(methodName);
        attackMethodInfo?.Invoke(obj, null); // 예외처리 그 함수이름이 없다면 못찾아서 null이 될테니 실행안됨
    }
}