С динамическими типами данных сложилась ситуация, схожая с АОП. А именно – полезных примеров применения этой техники можно пересчитать по пальцам, и они достойны коллекционирования (одна из коллекций по АОП собрана mezastel здесь). Сегодня, я надеюсь, мы добавим ещё пару таких примеров.
На прошлой неделе, занимаясь в очередной раз замерами производительности, я подумал, что было бы хорошо, не писать один и тот же служебный код несколько раз (профайлера под рукой тоже не оказалось). А хотелось бы иметь для исследуемых классов прокси классы, которые выполняли бы задачи делегации вызовов и замера производительности, и позволили бы сосредоточиться на алгоритме. Но писать это вручную или прибегать к помощи сторонних средств я не хотел. Следовательно, нужно было ответить на вопрос о том, как поддержать контракт каждого класса минимальных количеством кода, да ещё вставить код измерения производительности.
И тут в голову пришла мысль, что “поддержать” контракт поможет динамический тип данных. А во время вызова конечного члена класса можно вставить любой служебный код. Сказано – сделано! Предположим, что у нас есть такая доменная модель:
Теперь реализуем базовый динамический объект (в иерархии не было особой нужды, но, по привычке, сразу заложил основу для расширения):
Далее наш конечный объект, который занимает вызовом метода и замером производительности. Как вы видите, это он представляет собой реализацию паттерна Обёртка, а не паттерна Прокси:
И тестовый код:
Остаток дня я провёл, размышляя над тем, где ещё это может пригодиться. Придя на работу на следующее утро, увидел в rss-ленте похожий подход от Daniel Cazzulino. Он и David Ebbo используют такой подход для упрощения рефлексии и доступа к приватным членам. В их решении код поиска членов более универсален и точен, поэтому настоятельно рекомендую его изучить. Также мне было приятно ещё раз подтвердить высказывание, что одна и та же умная мысль может прийти в голову сразу нескольким людям.
На прошлой неделе, занимаясь в очередной раз замерами производительности, я подумал, что было бы хорошо, не писать один и тот же служебный код несколько раз (профайлера под рукой тоже не оказалось). А хотелось бы иметь для исследуемых классов прокси классы, которые выполняли бы задачи делегации вызовов и замера производительности, и позволили бы сосредоточиться на алгоритме. Но писать это вручную или прибегать к помощи сторонних средств я не хотел. Следовательно, нужно было ответить на вопрос о том, как поддержать контракт каждого класса минимальных количеством кода, да ещё вставить код измерения производительности.
И тут в голову пришла мысль, что “поддержать” контракт поможет динамический тип данных. А во время вызова конечного члена класса можно вставить любой служебный код. Сказано – сделано! Предположим, что у нас есть такая доменная модель:
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 используют такой подход для упрощения рефлексии и доступа к приватным членам. В их решении код поиска членов более универсален и точен, поэтому настоятельно рекомендую его изучить. Также мне было приятно ещё раз подтвердить высказывание, что одна и та же умная мысль может прийти в голову сразу нескольким людям.