반응형

리플렉션의 MethodInfo.Invoke는 속도가 많이 느리다.

이를 개선하기 위해서는 코드 제너레이터를 통하여 코드를 생성하는 방법과 델리게이트를 이용한 방법이 널리 알려져 있다.

method.CreateDelegate를 통하여 MethodInfo.Invoke 사용할 수도 있지만 이 방법은 객체가 생성이 된 후에 초기화가 가능하다.

.Net MVC에서 Controller는 표현식 트리(Expression Tree) 라는 것을 사용하여 리플렉션의 느린 속도를 개선하였다.

 

표현식 트리는 코드의 구조와 동작을 표현하는 데이터 구조이다. 주로 코드의 구문 분석, 변환, 또는 표현을 위해 사용된다.

 

Expression.Parameter: 메서드나 생성자에서 사용될 파라미터를 정의한다.
Expression.Call: 다른 메서드를 호출하는 표현식을 생성한다.
Expression.Lambda<Action<AA>>(methodCall, aaParam).Compile(): 표현식 트리를 델리게이트로 변환하고 컴파일한다.

이러한 방식으로 구성된 아래 코드는 MethodInfo invoke에 비해 훨씬 빠른 실행 속도를 보여준다.

 

using System;
using System.Diagnostics;
using System.Linq.Expressions;

namespace ConsoleApp
{
    public class AA
    {
        private int _index = 0;
        public void Call()
        {
            //Console.WriteLine($"{_index++}");
        }
    }

    class Program
    {
        delegate void CallDelegate();
        static void Main(string[] args)
        {
            var aaType = typeof(AA);
            var method = aaType.GetMethod("Call");
            Stopwatch sw = new Stopwatch();

            var a = new AA();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                a.Call();
            }
            sw.Stop();
            Console.WriteLine($"Direct call : {sw.Elapsed}");

            var b = new AA();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                method.Invoke(b, null);
            }
            sw.Stop();
            Console.WriteLine($"MethodInfo call : {sw.Elapsed}");

            // Delegate call
            var c = new AA();
            CallDelegate callDelegate = (CallDelegate)method.CreateDelegate(typeof(CallDelegate), c);
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                callDelegate();
            }
            sw.Stop();
            Console.WriteLine($"Delegate call : {sw.Elapsed}");

            var aaParam = Expression.Parameter(aaType, "instance");
            var methodCall = Expression.Call(aaParam, method);
            var actionLambda = Expression.Lambda<Action<AA>>(methodCall, aaParam).Compile();

            var d = new AA();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                actionLambda(d);
            }
            sw.Stop();
            Console.WriteLine($"Expression call : {sw.Elapsed}");

            Console.ReadLine();
        }
    }
}

예시) 상속받은 클래스의 Event를 리플렉션으로 탐색하고 캐시하여 RaiseEvent 메소드 호출시 자동으로 Event 호출 까지 실행해준다.

public abstract class EventProtocolHandlerBase
{
    private readonly Dictionary<Type, Action<INotifyEventArgs>> _eventHandlers = new Dictionary<Type, Action<INotifyEventArgs>>();
    public EventProtocolHandlerBase()
    {
        RegisterEvents(GetType());
    }
    public void RaiseEvent<T>(T eventArgs) where T : INotifyEventArgs
    {
        if (_eventHandlers.TryGetValue(typeof(T), out var handler))
        {
            if (handler is Action<T> action)
            {
                Add(() => { action(eventArgs); });
            }
        }
        else
        {
            LogHelper.Error($"not found event type! {typeof(T).Name}");
        }
    }
    public void RegisterEvents(Type type)
    {
        foreach (var eventInfo in type.GetEvents())
        {
            var argumentType = eventInfo.EventHandlerType.GetGenericArguments()[0];
            var eventParameter = Expression.Parameter(typeof(INotifyEventArgs), "args");
            var castArg = Expression.TypeAs(eventParameter, argumentType);

            var eventFieldExpr = Expression.Field(Expression.Constant(this), eventInfo.Name);
            var invokeExpr = Expression.Invoke(eventFieldExpr, castArg);

            var notNullCheck = Expression.NotEqual(eventFieldExpr, Expression.Constant(null, typeof(Action<>).MakeGenericType(argumentType)));
            var conditionalExpr = Expression.IfThen(notNullCheck, invokeExpr);
            var lambda = Expression.Lambda<Action<INotifyEventArgs>>(conditionalExpr, eventParameter);
            var action = lambda.Compile();
            RegisterEvent(argumentType, action);
        }
    }
    public void RegisterEvent(Type type, Action<INotifyEventArgs> action)
    {
        _eventHandlers[type] = action;
    }
}
반응형

+ Recent posts