본문 바로가기

C#/Study

[복습] Reflection - C#, 리플렉션

반응형

리플렉션

  • 객체의 형식(Type) 정보를 들여다보는 기능이다.
  • 이 기능을 이용하면 프로그램 실행 중에 객체의 형식 이름부터 프로퍼티 목록, 메서드 목록, 필드, 이벤트 목록까지 모두 열어볼 수 있다.
  • 형식의 이름만 있다면 동적으로 인스턴스를 만들 수 있다.
  • 새로운 데이터 형식을 동적으로 만들 수도 있다.

 

Object.GetType() 메서드와 Type 클래스

  •  Object는 모든 데이터 형식의 조상이기 때문에 모든 데이터 형식은 다음 메서드를 물려받는다.
    • Equals()
    • GetHashCode()
    • GetType()
    • ReferenceEquals()
    • ToString()
  • 위의 메서드 중 GetType() 메서드를 이용해서 객체의 형식 정보를 얻어낼 수 있다.
  • GetType() 메서드는 Type 형식의 결과를 반환한다.
  • Type 형식은 다음과 같은 데이터 형식의 정보를 담고있다.
    • 형식 이름
    • 소속되어 있는 어셈블리 이름
    • 프로퍼티 목록
    • 메서드 목록
    • 필드 목록
    • 이벤트 목록
    • 인터페이스의 목록

예제 - 간단한 리플렉션 사용

int a = 0;

Type type = a.GetType();
FieldInfo[] fields = type.GetFields(); // 필드 목록 조회

foreach (var field in fields)
{
    Console.WriteLine("Type:{0}, Name:{1}", field.FieldType.Name, field.Name);
}

  • int에 커서를 두고 F12로 확인한 결과 field 값은 MaxValue와 MinValue만 있는걸 확인할 수 있다.
  • 해당 형식의 필드 목록을 반환하는 GetFields 메서드를 이용해서 Int32에 있는 필드 목록을 출력한 예제다.
메서드 반환 형식 설명
GetConstructors() ConstructorInfo[] 해당 형식의 모든 생성자 목록을 반환한다.
GetEvents() EventInfo[] 해당 형식의 이벤트 목록을 반환한다.
GetFields() FieldInfo[] 해당 형식의 필드 목록을 반환한다.
GetGenericArguments() Type[] 해당 형식의 형식 매개 변수 목록을 반환한다.
GetInterfaces() Type[] 해당 형식의 상속하는 인터페이스 목록을 반환한다.
GetMembers() MemberInfo[] 해당 형식의 멤버 목록을 반환한다.
GetMethods() MethodInfo[] 해당 형식의 메서드 목록을 반환한다.
GetNestedTypes() Type[] 해당 형식의 내장 형식 목록을 반환한다.
GetProperties() PropertyInfo[] 해당 형식의 프로퍼티 목록을 반환한다.

예제 - 검색 옵션 입력

Type type = a.GetType();

// public 인스턴스 필드 조회
var fields1 = type.GetFields(BindingFlags.Public | BindingFlags.Instance);

// 비(非) public 인스턴스 필드 조회
var fields2 = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

// public 정적 필드 조회
var fields3 = type.GetFields(BindingFlags.Public | BindingFlags.Static);

// 비(非) public 정적 필드 조회
var fields4 = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static);
  • 위의 검색옵션을 통해 public 형태의 항목을 조회할지 말지, static 형태의 항목을 조회할지 말지를 정할 수 있다.

 

리플렉션을 이용한 객체 생성 및 사용방법

  • 리플렉션을 이용해서 동적으로 인스턴스를 만들기 위해서는 System.Activator 클래스가 필요하다.
  • 인스턴스를 만들고자 하는 형식의 Type 객체를 매개 변수로 넘기면, Activator.CreateInstance() 메서드는 입력받은 형식의 인스턴스를 생성하여 반환한다.
object a = Activator.CreateInstance(typeof(int));
  • 객체의 프로퍼티에 값을 할당하는 것도 동적으로 할 수 있다.
  • Type.GetProperties()의 반환 형식인 PropertyInfo 객체는 SetValue()와 GetValue() 메서드를 갖고 있다.
  • GetValue()를 호출하면 프로퍼티로부터 값을 읽을 수 있고, SetValue()를 호출하면 프로퍼티에 값을 할당할 수 있다.

예제 - 프로퍼티 GetValue, SetValue 메서드 사용 O

  • Profile 클래스
class Profile
{
    public string Name { get; set; }
    public string Phone { get; set; }
}
  • 코드
Type type = typeof(Profile);
Object profile = Activator.CreateInstance(type);

PropertyInfo name = type.GetProperty("Name"); // 1
PropertyInfo phone = type.GetProperty("Phone");

name.SetValue(profile, "박찬호", null); // 2
phone.SetValue(profile, "123-4567", null);

Console.WriteLine("{0}, {1}", name.GetValue(profile, null), phone.GetValue(profile, null));
  • Type.GetProperty() 메서드는 특정 이름의 프로퍼티를 찾아 그 프로퍼티의 정보를 담은 하나의 PropertyInfo 객체만을 반환한다.
  • SetValue()와 GetValue()의 마지막 매개 변수는 인덱서의 인덱스를 위해 사용된다. 프로퍼티는 인덱서가 필요없으므로 위 예제에서 null로 할당해주었다.

 

인덱서란?

2020/12/21 - [복습] 인덱서 - C#

 

[복습] 인덱서 - C#

인덱서 인덱서에서는 클래스나 구조체의 인스턴스를 배열처럼 인덱싱할 수 있다. 클래스나 구조체는 배열이 아니지만, 배열처럼 []를 사용하여 내부 필드값에 접근할 수 있다. 인덱서는 다음과

zzangwoo.tistory.com

 

예제 - 프로퍼티 GetValue, SetValue 메서드 사용 X

  • Profile 클래스
class Profile
{
    public string Name { get; set; }
    public string Phone { get; set; }

    public void Print()
    {
        Console.WriteLine("{0}, {1}", Name, Phone);
    }
}
  • 코드
Type type = typeof(Profile);
Profile profile = Activator.CreateInstance(type) as Profile; // 1

profile.Name = "박찬호";
profile.Phone = "123-4567";

MethodInfo method = type.GetMethod("Print"); // 2

method.Invoke(profile, null); //3
  • 직접 객체 생성하려는 클래스로 형 변환을 한 후에 프로퍼티에 접근이 가능하다.
  • MethodInfo 클래스를 이용하면 메서드도 사용할 수 있다.
  • null 매개 변수가 오는 자리에는 Invoke() 메서드가 호출할 메서드의 매개 변수가 와야 한다. Profile.Print() 메서드는 매개 변수가 없기 때문에 null을 넘겨주었다.

 

리플렉션 활용 (뇌피셜)

  • 지금까지 공부한 내용인 리플렉션, 인덱서를 바탕으로 xml을 파싱하고 파싱한 내용을 출력하는 코드를 작성해보았다. (이러한 상황에 리플렉션이 활용될거다 라는 내 뇌피셜을 바탕으로 만들었다.)

xml 데이터

<?xml version="1.0" encoding="UTF-8"?>
<PersonalInfo>
	<person code="CM001">
		<name>홍길동</name>
		<sex>남자</sex>
		<regNod>123456-1234567</regNod>
		<tel>02-123-1234</tel>
		<address>
			서울
		</address>
	</person>
	<person code="CM002">
		<name>김길수</name>
		<sex>남자</sex>
		<regNod>654321-7654321</regNod>
		<tel>03-321-4321</tel>
		<address>
			부산
		</address>
	</person>	
	<person code="CF001">
		<name>홍길자</name>
		<sex>여자</sex>
		<regNod>121212-2323232</regNod>
		<tel>031-1111-2222</tel>
		<address>
			인천
		</address>
	</person>
</PersonalInfo>
  • 위의 데이터에서 person 태그의 code 속성을 기준으로 name, sex, regNode, tel, address의 값을 인덱서를 이용하여 클래스에 저장할 예정이다.

인덱서 및 인적 데이터를 저장할 클래스

class PersonalInfo : IEnumerable
{
    private Dictionary<string, Person> data = new Dictionary<string, Person>();

    [IndexerNameAttribute("PersonIndexer")]
    public Person this[string code]
    {
        get
        {
            if (data.ContainsKey(code))
            {
                return data[code];
            }
            else
            {
                throw new Exception("없는 code입니다.");
            }
        }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("인덱서 오류");
            }
            else
            {
                if (data.ContainsKey(code))
                {
                    data[code] = value;
                }
                else
                {
                    data.Add(code, value);
                }
            }
        }
    }

    /// <summary>
    /// IEnumerable 구현
    /// </summary>
    /// <returns></returns>
    public IEnumerator GetEnumerator()
    {
        foreach (var result in data)
        {
            // yield : 데이터를 하나씩 리턴할 때 사용
            yield return result.Value;
        }
    }
}

class Person
{
    public string name { get; set; }
    public string sex { get; set; }
    public string regNod { get; set; }
    public string tel { get; set; }
    public string address { get; set; }
}

xml 파싱 및 파싱한 내용 출력 코드

XmlDocument xml = new XmlDocument();
xml.Load(@"D:\Study\C#_Restudy\CSharp_Study\Reflection_Example\Reflection_Example\AddressBook.xml");

XmlNodeList xmlList = xml.SelectNodes("PersonalInfo/person");

PersonalInfo personalInfo = new PersonalInfo();
foreach (XmlNode items in xmlList)
{
    PropertyInfo propertyInfo = typeof(PersonalInfo).GetProperty("PersonIndexer");
    string code = items.Attributes["code"].Value;

    Person person = new Person();
    foreach(XmlNode item in items.ChildNodes)
    {
        person.GetType().GetProperty(item.Name).SetValue(person, item.InnerText.Trim(), null);
    }
    propertyInfo.SetValue(personalInfo, person, new object[] { code });
}

// 출력 
foreach (var personData in personalInfo)
{
    var personList = personData.GetType().GetProperties();
    foreach (var item in personList)
    {
        Console.WriteLine($"{item.Name} : {item.GetValue(personData, null)}");
    }
    Console.WriteLine();
}

결과

반응형