[객체지향] 10.Reflection과 고급 메타프로그래밍, Reflection의 원리와 활용

1. 서론

C# 언어와 .NET 플랫폼은 고급 개발자들이 효율적으로 프로그래밍을 할 수 있게 해주는 여러 가지 기능을 제공합니다. 그중에 Reflection은 클래스, 메서드, 프로퍼티 등 객체의 메타데이터를 탐색하고 활용할 수 있는 강력한 메커니즘입니다. Reflection을 통해 개발자는 코드의 구조를 동적으로 조작하거나, 런타임 중에 타입을 검사하고, 객체를 생성하는 등 다양한 고급 메타프로그래밍 기법을 사용할 수 있습니다. 이 글에서는 Reflection의 원리, 활용 방법, 그리고 실제 예제들을 살펴보겠습니다.

2. Reflection의 기본 원리

Reflection은 .NET의 System.Reflection 네임스페이스에 포함된 클래스를 통해 구현됩니다. 기본적으로 Reflection은 다음과 같은 기능을 제공합니다:

  • 타입 정보를 얻어오기
  • 메서드, 속성, 필드 등의 정보를 동적으로 호출하거나 수정하기
  • 인스턴스를 동적으로 생성하기
  • 애트리뷰트를 통해 메타데이터에 접근하기

Reflection을 사용하면 컴파일 타임에 알 수 없는 정보들을 런타임에 추출할 수 있습니다. 예를 들어, 동적으로 로딩된 어셈블리의 타입 정보를 미리 알지 못하더라도 Reflection을 통해 해당 정보에 접근할 수 있습니다.

2.1 Reflection의 주요 클래스

Assembly: 어셈블리에 대한 정보 제공
Type: 타입에 대한 정보를 제공
MethodInfo: 메서드 정보 제공
PropertyInfo: 프로퍼티 정보 제공
FieldInfo: 필드 정보 제공
ConstructorInfo: 생성자 정보 제공

3. Reflection의 활용

Reflection의 다양한 활용 사례를 살펴보겠습니다. 이 섹션에서는 인스턴스 생성, 메서드 호출, 속성 접근 및 애트리뷰트 사용 예제를 다룰 것입니다.

3.1 인스턴스 생성

Reflection을 사용하여 객체를 동적으로 생성할 수 있습니다. 아래는 SampleClass라는 클래스를 정의하고 Reflection을 사용하여 인스턴스를 생성하는 예제입니다.


    using System;
    using System.Reflection;

    public class SampleClass
    {
        public string Name { get; set; }

        public SampleClass(string name)
        {
            Name = name;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type sampleType = typeof(SampleClass);
            ConstructorInfo constructor = sampleType.GetConstructor(new Type[] { typeof(string) });
            object instance = constructor.Invoke(new object[] { "Reflection Example" });

            Console.WriteLine(((SampleClass)instance).Name);
        }
    }
    

3.2 메서드 호출

Reflection을 통해 메서드를 동적으로 호출할 수 있습니다. 아래의 예제는 메서드를 호출하는 방법을 보여줍니다.


    public class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type calculatorType = typeof(Calculator);
            object calculatorInstance = Activator.CreateInstance(calculatorType);
            MethodInfo addMethod = calculatorType.GetMethod("Add");

            object result = addMethod.Invoke(calculatorInstance, new object[] { 5, 10 });
            Console.WriteLine("Addition Result: " + result);
        }
    }
    

3.3 속성 접근

Reflection을 사용하여 객체의 속성에 접근할 수도 있습니다. 아래의 예제는 속성 값을 읽고 변경하는 방법을 보여줍니다.


    public class Person
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person();
            Type personType = typeof(Person);

            PropertyInfo nameProperty = personType.GetProperty("Name");
            nameProperty.SetValue(person, "John Doe");
            Console.WriteLine("Person Name: " + nameProperty.GetValue(person));
        }
    }
    

3.4 애트리뷰트 사용

클래스나 메서드에 붙은 애트리뷰트는 Reflection을 통해 쉽게 접근할 수 있습니다. 아래는 사용자 정의 애트리뷰트를 정의하고 그것에 접근하는 예제입니다.


    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthorAttribute : Attribute
    {
        public string Name { get; }
        public AuthorAttribute(string name)
        {
            Name = name;
        }
    }

    [Author("Jane Doe")]
    public class Sample
    {
        [Author("John Smith")]
        public void SampleMethod() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type sampleType = typeof(Sample);
            AuthorAttribute classAttribute = (AuthorAttribute)Attribute.GetCustomAttribute(sampleType, typeof(AuthorAttribute));
            Console.WriteLine("Class Author: " + classAttribute.Name);

            MethodInfo methodInfo = sampleType.GetMethod("SampleMethod");
            AuthorAttribute methodAttribute = (AuthorAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(AuthorAttribute));
            Console.WriteLine("Method Author: " + methodAttribute.Name);
        }
    }
    

4. 고급 메타프로그래밍 기법

Reflection을 활용한 고급 메타프로그래밍의 예시는 많습니다. 여기에는 플러그인 아키텍처, ORM(Object-Relational Mapping), 동적 프록시 생성 등 여러 가지가 포함됩니다.

4.1 플러그인 아키텍처

Reflection을 사용하여 런타임 시 동적으로 플러그인을 로드하고 사용할 수 있습니다. 이를 통해 애플리케이션의 기능을 쉽게 확장할 수 있습니다.


    using System;
    using System.IO;
    using System.Reflection;

    public interface IPlugin
    {
        void Execute();
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 플러그인 DLL을 동적으로 로드
            string pluginPath = "path/to/plugin.dll"; // 실제 플러그인 DLL 경로로 바꿔주세요
            Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
            Type[] pluginTypes = pluginAssembly.GetTypes();

            foreach (Type type in pluginTypes)
            {
                if (typeof(IPlugin).IsAssignableFrom(type))
                {
                    IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
                    plugin.Execute();
                }
            }
        }
    }
    

4.2 ORM(Object-Relational Mapping)

Reflection을 사용하여 데이터베이스와 객체 간의 매핑을 자동으로 수행하는 ORM 기능을 구현할 수 있습니다. 아래는 간단한 ORM 예제입니다.


    using System;
    using System.Collections.Generic;
    using System.Data.SqlClient;

    public class SqlMapper
    {
        private string connectionString;

        public SqlMapper(string connectionString)
        {
            this.connectionString = connectionString;
        }

        public List Query(string sql) where T : new()
        {
            List result = new List();

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                SqlCommand cmd = new SqlCommand(sql, conn);
                conn.Open();
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        T item = new T();
                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            PropertyInfo prop = typeof(T).GetProperty(reader.GetName(i));
                            if (prop != null && prop.CanWrite)
                            {
                                prop.SetValue(item, reader.GetValue(i));
                            }
                        }
                        result.Add(item);
                    }
                }
            }
            return result;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SqlMapper sqlMapper = new SqlMapper("YourConnectionString");
            var users = sqlMapper.Query("SELECT * FROM Users");
        }
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    

5. 결론

Reflection은 C#에서 강력한 메타프로그래밍 도구로, 런타임에 타입에 대한 정보에 접근하고 조작할 수 있게 해줍니다. 이를 통해 우리는 코드의 유연성을 높이고, 애플리케이션의 기능을 모듈화하고 확장할 수 있습니다. 이 글에서 소개한 예제와 개념들 외에도 Reflection은 각종 프레임워크 및 라이브러리에서도 광범위하게 활용되고 있어, 그 중요성을 알 수 있습니다. 앞으로도 Reflection과 메타프로그래밍에 대한 깊이 있는 이해와 활용이 중요할 것입니다.