Как стать автором
Обновить

Измеряем производительность с помощью DynamicObject

Время на прочтение7 мин
Количество просмотров966
С динамическими типами данных сложилась ситуация, схожая с АОП. А именно – полезных примеров применения этой техники можно пересчитать по пальцам, и они достойны коллекционирования (одна из коллекций по АОП собрана mezastel здесь). Сегодня, я надеюсь, мы добавим ещё пару таких примеров.


На прошлой неделе, занимаясь в очередной раз замерами производительности, я подумал, что было бы хорошо, не писать один и тот же служебный код несколько раз (профайлера под рукой тоже не оказалось). А хотелось бы иметь для исследуемых классов прокси классы, которые выполняли бы задачи делегации вызовов и замера производительности, и позволили бы сосредоточиться на алгоритме. Но писать это вручную или прибегать к помощи сторонних средств я не хотел. Следовательно, нужно было ответить на вопрос о том, как поддержать контракт каждого класса минимальных количеством кода, да ещё вставить код измерения производительности.
И тут в голову пришла мысль, что “поддержать” контракт поможет динамический тип данных. А во время вызова конечного члена класса можно вставить любой служебный код. Сказано – сделано! Предположим, что у нас есть такая доменная модель:
using System;
using System.Threading;

namespace DynamicWrapperTest
{
    public class Payment
    {
        public Guid Id { get; set; }
        public Guid UserId { get; set; }
        public Guid OperationId { get; set; }
        public decimal Amount { get; set; }
        public string Description { get; set; }
    }

    public class PaymentService
    {
        private readonly Random rand = new Random(Environment.TickCount);

        public void MakePayment(Payment payment)
        {
            Thread.Sleep(TimeSpan.FromSeconds(rand.Next(5)));
        }
    }
}


* This source code was highlighted with Source Code Highlighter.

Теперь реализуем базовый динамический объект (в иерархии не было особой нужды, но, по привычке, сразу заложил основу для расширения):
using System;
using System.Dynamic;

namespace DynamicWrapperTest
{
    public class DynamicWrapper : DynamicObject
    {
        private readonly object source;

        public DynamicWrapper(object source)
        {
            this.source = source;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            var methodInfo = source.GetType().GetMethod(binder.Name);
            if (methodInfo != null)
            {
                Func<object, object[], object> func = (s, a) => methodInfo.Invoke(s, a);

                result = MethodCall(func, source, args);
                
                return true;
            }

            result = null;
            
            return false;
        }

        protected virtual object MethodCall(Func<object, object[], object> func, object src, object[] args)
        {
            return func(src, args);
        }
    }
}


* This source code was highlighted with Source Code Highlighter.

Далее наш конечный объект, который занимает вызовом метода и замером производительности. Как вы видите, это он представляет собой реализацию паттерна Обёртка, а не паттерна Прокси:
using System;
using System.Diagnostics;
using System.IO;

namespace DynamicWrapperTest
{
    public class ProfilerWrapper : DynamicWrapper
    {
        private readonly int iterationCount;
        private readonly TextWriter output;
        private readonly Stopwatch stopwatch = new Stopwatch();

        public ProfilerWrapper(object source, int iterationCount, TextWriter output)
            : base(source)
        {
            this.iterationCount = iterationCount;
            this.output = output;
        }

        protected override object MethodCall(Func<object,object[],object> func, object src, object[] args)
        {
            object result = null;

            for (var i = 0; i < iterationCount; i++)
            {
                stopwatch.Restart();

                result = base.MethodCall(func, src, args);

                stopwatch.Stop();
                output.WriteLine("Step #{0} : Method call in {1} ms", i + 1, stopwatch.Elapsed.TotalMilliseconds);
            }

            return result;
        }
    }
}


* This source code was highlighted with Source Code Highlighter.

И тестовый код:
using System;

namespace DynamicWrapperTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var payment = new Payment
                        {
                            Id = Guid.NewGuid(),
                            UserId = Guid.NewGuid(),
                            OperationId = Guid.NewGuid(),
                            Amount = 500m,
                            Description = "Feed developers plz!!!"
                        };

            var paymentService = new PaymentService();
            dynamic dynamicWrapper = new ProfilerWrapper(paymentService, 10, Console.Out);

            dynamicWrapper.MakePayment(payment);

            Console.ReadKey();
        }
    }
}

* This source code was highlighted with Source Code Highlighter.

Остаток дня я провёл, размышляя над тем, где ещё это может пригодиться. Придя на работу на следующее утро, увидел в rss-ленте похожий подход от Daniel Cazzulino. Он и David Ebbo используют такой подход для упрощения рефлексии и доступа к приватным членам. В их решении код поиска членов более универсален и точен, поэтому настоятельно рекомендую его изучить. Также мне было приятно ещё раз подтвердить высказывание, что одна и та же умная мысль может прийти в голову сразу нескольким людям.
Теги:
Хабы:
Всего голосов 28: ↑22 и ↓6+16
Комментарии31

Публикации

Истории

Работа

.NET разработчик
80 вакансий

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
26 октября
ProIT Network Fest
Санкт-Петербург
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань