DispatchProxy를 사용하여 C#의 인터페이스로 모든 클래스 프록시

나는 최근에 NHibernate ~ PostgreSQL 를 통해 수행되는 성능 테스트 데이터베이스 호출 방법을 알아내는 임무를 받았습니다. 내가 찾을 수 있는 한 NHibernateSQL 및 각 쿼리의 실행 시간을 가로채서 수동으로 기록하는 정말 쉬운 방법을 제공하지 않습니다.

조금 더 파고든 후 사용자 정의 또는 타사 라이브러리의 모든 인터페이스를 쉽게 프록시할 수 있는 덜 알려진 편리한 도구를 발견했습니다. 이 도구는 System.Reflection.DispatchProxy nuget 패키지에 있습니다. 약간의 코드로 인터페이스를 구현하는 클래스의 모든 인스턴스를 프록시로 래핑하여 기록할 뿐만 아니라 인수와 반환된 데이터를 모두 조작할 수 있습니다.

예시



이 간단한 클래스와 인터페이스를 사용하십시오.

interface IHello
{
    bool SayHello(string name);
}

class Hello : IHello
{
    public bool SayHello(string name)
    {
        Console.WriteLine($"Hello {name}");
        return true;
    }
}


특별한 것은 없고 단순한 이름 매개변수를 사용하는 하나의 메소드만 노출되어 콘솔에 문자열을 쓰고 성공을 나타내는 bool을 반환합니다.

이 예에서는 고안되었지만 SayHello 로 가는 통화를 녹음하고 싶었습니다.

class HelloDispatchProxy<T> : DispatchProxy where T : class, IHello
{
    private IHello Target { get; set; }

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        // Code here to track time, log call etc.
        var result = targetMethod.Invoke(Target, args);
        return result;
    }

    public static T CreateProxy(T target)
    {
        var proxy = Create<T, HelloDispatchProxy<T>>() as HelloDispatchProxy<T>;
        proxy.Target = target;
        return proxy as T;
    }
}


이것은 우리의 DispatchProxy , HelloDispatchProxy 입니다. 사용법은 꽤 간단합니다.

IHello hello = HelloDispatchProxy<IHello>.CreateProxy(new Hello());

CreateProxy 메서드는 기본 추상 클래스에서 Create<T>를 호출하여 프록시 개체를 생성합니다. 그런 다음 대상 인스턴스는 Target 속성에 할당된 후 프록시 캐스트를 T(이 인스턴스에서는 IHello) 유형으로 반환합니다.

그러면 프록시된IHello 인스턴스에서 호출되는 모든 메서드가 Invoke() 트리거됩니다. 대상 메서드를 호출하고 매개 변수를 전달하고 결과를 반환하는 것은 프록시의 책임입니다. 이 방법에서는 원하는 모든 작업을 수행할 수 있으며 원본Target 인스턴스에도 액세스할 수 있습니다.

GitHub에서 이에 대한 완전한 예를 볼 수 있습니다.

With great power comes great responsibility



이렇게 하면 많은 제어가 가능하므로 args 및 반환된 결과로 수행하는 작업에 주의하십시오. 그렇지 않으면 예기치 않은 동작이 발생할 수 있습니다.

성능 시험


DispatchProxySystem.Reflection 네임스페이스 아래에 있습니다. 이 때문에 이 접근 방식에서 어떤 오버헤드가 발생하는지 궁금했습니다. 직접 테스트하는 것보다 더 나은 이해 방법은 무엇입니까?

프록시되지 않은 인스턴스와 프록시된 인스턴스 모두에 대한 호출 간의 원시 비교를 제공하기 위해 slightly over-engineered set of tests을 설정했습니다. 결과를 분석하기 전에 평균을 얻기 위해 여러 번 실행했습니다.

    100000 W/O  100000 W    % Increase
    2233.023    2382.8835   6.71%
    2235.5306   2398.1216   7.27%
    2236.2449   2386.4416   6.72%
    2236.8529   2389.209    6.81%
    2239.9692   2402.4627   7.25%
    2227.0108   2367.0306   6.29%
    2237.8432   2384.8429   6.57%
    2233.5528   2392.8394   7.13%
    2238.411    2398.3124   7.14%
    2235.7398   2388.134    6.82%
    2262.3454   2380.0595   5.20%
    2340.0243   2385.0553   1.92%
    2249.3693   2383.6925   5.97%
    2246.9835   2381.507    5.99%
    2240.7711   2381.4675   6.28%
    2232.5296   2374.0465   6.34%
    2248.0095   2359.8636   4.98%
    2250.2518   2394.8422   6.43%
    2234.7757   2387.8114   6.85%
    2238.7645   2387.8047   6.66%


    0.00112245  0.001192661 6.27%

따라서 평균적으로 프록시만 도입하면 실행에 6% 정도가 추가로 소요되었습니다. 이 현재 상태에서 프록시는 전혀 유용한 작업을 수행하지 않지만 컨텍스트에 효율성을 부여하는 데 도움이 됩니다.

그러나 프록시에 차단 콘솔 쓰기 라인과 같이 느린 것을 추가하면 % 증가가 6.27%에서 100% 이상으로 치솟습니다.

System.Console.WriteLine($"Going to call {targetMethod.Name}");


성능에 미치는 영향에서 가장 중요한 부분은 Invoke 메서드에서 무엇을 하기로 결정하느냐 입니다.

성능이 가장 중요한 응용 프로그램을 제외한 모든 응용 프로그램에서 프록시 자체의 오버헤드는 미미합니다. 예를 들어 HTTP 웹 요청 또는 메시지 처리기와 같은 컨텍스트에서 2-3개의 DB 호출을 프록시하는 경우 영향은 무시할 수 있습니다. 사실, 실제 응용 프로그램을 사용한 테스트에서 숫자는 APM 도구를 추적할 때도 허용 가능한 오차 범위 내에 있습니다.

최대 절전 모드 및 PostgreSQL



저처럼 NHibernate에서 정보를 수집하는 데 특히 관심이 있다면 사용자 지정 드라이버 구현에서 IDBCommand를 래핑하여 이 작업을 수행할 수 있습니다.

public class ProfiledNpgsqlDriver : NpgsqlDriver {
    public override IDbCommand CreateCommand() {
        var command = base.CreateCommand();

        if (ConfigurationManager.AppSettings["EnableDBTracing"]?.ToLower() == "true") {
            command = ProfiledDbCommandDispatchProxy<IDbCommand>.CreateProxy(command);
        }

        return command;
    }
}


대상IDBCommandCommand 속성 및 기록 시간 등을 StopWatch 로 실행 중인 명령에 접근할 수 있습니다. 한 단계 더 나아가 프록시를 IObservable<T> 으로 구현하여 유스케이스에 따라 다르게 처리할 수 있도록 했습니다.

마무리



바라건대, 당신이 이것을 흥미롭게 찾았습니다. 이에 대한 좋은 사용 사례가 생각나거나 의견이 있습니까? 듣고 싶습니다 ❤

좋은 웹페이지 즐겨찾기