[C#] 50. Reflection 기능을 사용하는 방법 - Method


Study/C#  2021. 10. 14. 15:41

안녕하세요. 명월입니다.


이 글은 C#에서 Reflection 기능을 사용하는 방법 - Method에 대한 글입니다.


이전 글에서 Reflection의 기능을 이용해서 클래스의 인스턴스를 생성하는 방법에 대해 설명했습니다.

link - [C#] 49. Reflection 기능을 사용하는 방법 - Class


Reflection이라는 것은 간단하게 설명하면 기존에 소스에서 new 키워드를 사용하여 인스턴스를 생성하는 방식에서 String의 값의 요소에 의해 동적으로 인스턴스가 생성되는 것을 이야기합니다.

함수(Method)도 기존에 소스 상에서 함수명을 작성하여 호출하는 방식이 아닌 클래스 내에서 함수를 탐색하여 동적으로 실행하도록 하는 방법을 뜻합니다.

using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // 함수 호출
      node.Print();
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 방식은 기존 방식으로 Print 함수를 호출하는 방법입니다.

using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스에서 Print 함수를 취득
      var method = type.GetMethod("Print");
      // Print 함수에 인스턴스를 넣어 실행한다.
      method.Invoke(node, null);
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제는 Reflection로 함수를 찾아서 실행하는 방식입니다. 소스 상에서는 String의 데이터로 Print의 값으로 함수를 실행하게 되네요.

using System;
using System.Linq;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 출력 함수
    public void A()
    {
      // 콘솔 출력
      Console.WriteLine("A");
    }
    // 출력 함수
    public void B()
    {
      // 콘솔 출력
      Console.WriteLine("B");
    }
    // 출력 함수
    public void C()
    {
      // 콘솔 출력
      Console.WriteLine("C");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스를 모두 취득하기
      foreach(var method in type.GetMethods().Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(void)))
      {
        // 함수를 실행하기
        method.Invoke(node, null);
      }
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 GetMethods를 이용해서 Node 클래스에 있는 모든 함수를 취득해 왔습니다.

그런데 C#에서의 모든 클래스는 Object 클래스를 상속받습니다. 그렇기 때문에 Object의 함수들도 실행이 됩니다. 그래서 파라미터가 없고 return 타입이 void인 것만 걸러서 실행을 했습니다.

결과는 A,B,C의 함수가 실행됐습니다.


저는 예제를 위해서 위처럼 작성했지만, Reflection을 이용하면 유저의 입력 값 또는 데이터 베이스나 기타 파일의 값에 의해 실행할 수 있는 함수를 제어할 수 있겠네요. 간단하게 인터프리트 패턴(Interpret pattern)을 만들 수 있습니다.

link - [Design pattern] 인터프리트 패턴(Interpret pattern) (※스택 계산기 예제)


그리고 Reflection을 이용하면 public으로 된 함수 뿐 아니라 private, protected의 접근 제한자도 접근할 수 있습니다.

using System;
using System.Reflection;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수(private 제한자로 설정)
    private void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스에서 Print 함수를 취득, private, protected, public에 관계없이 Instance 함수를 취득한다.
      var method = type.GetMethod("Print", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
      // Print 함수에 인스턴스를 넣어 실행한다.
      method.Invoke(node, null);
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제에서 Print 함수는 private로 설정이 되어 있는 데도 Main 함수에서 실행이 되는 것을 확인할 수 있습니다.


우리가 완벽한 캡슐화로 프로젝트를 작성해도 Unit 테스트를 위해서 중간 함수가 제대로 작동하는 지 테스트를 해야 하는 경우가 있습니다.

그럴 경우, Reflection을 이용한 TestClass를 작성해서 클래스의 Unit 테스트를 하게 된다면 소스를 건드리지 않고 테스트 클래스를 만드는 것도 가능합니다.


그리고 static과 파라미터가 있는 함수에도 접근이 가능합니다.

using System;
using System.Reflection;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수(private 제한자로 설정)
    private static void Print(String str)
    {
      // 콘솔 출력
      Console.WriteLine(str);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스에서 Print 함수를 취득, private, protected, public에 관계없이 Static 함수를 취득한다.
      var method = type.GetMethod("Print", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
      // Print 함수는 static 함수이기 때문에 인스턴스 값이 필요없다., 파라미터는 배열 타입인데, 파라미터가 한개 이기 때문에 하나의 값만 넣는다.
      method.Invoke(null, new object[] { "Hello world" });
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제는 Node 클래스의 Print의 static 함수입니다. static 함수는 인스턴스에 상관없이 호출이 되는 함수입니다.

그래서 Invoke함수에서 함수를 호출할 때, 인스턴스의 값 대신에 null를 넣습니다. 파라미터의 값은 Invoke 함수의 두번째 파라미터로 배열 형식으로 입력이 됩니다.


Reflection의 기능를 잘 이용하면 프로그램을 꽤 유연하게 작성할 수 있는 장점이 있습니다.

역시 Class와 마찬가지고 디자인 패턴과 관계가 많은 기능이고 특히, NUnit의 Unit 테스트에서 가장 많이 사용되는 기능이기도 합니다.


그런데 Reflection기능은 Visual studio의 디버그 환경에서 잡아주지를 않기 때문에, 버그의 위험성과 가독성의 많이 떨어지는 단점이 있습니다.

그리고 일반 정적인 호출보다는 탐색하고 인스턴스를 넣어 실행하는 구조이기 때문에 성능에도 많은 영향을 끼치게 됩니다. 역시 사양에 맞게 사용하는 것이 가장 좋을 듯 싶습니다.


여기까지 C#에서 Reflection 기능을 사용하는 방법 - Method에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.