Warning: include(/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/top-cache.php): failed to open stream: No such file or directory in /volume1/web/cyberhost.biz/index.php on line 9
Call Stack:
0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0
Warning: include(): Failed opening '/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/top-cache.php' for inclusion (include_path='.:/usr/share/pear') in /volume1/web/cyberhost.biz/index.php on line 9
Call Stack:
0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0
Pipelining в C#-приложениях | Хостинг за 90 р. от cyberhost.biz — платный хостинг
В мире функционального программирования существует мощная концепция композиции функций. В C# тоже можно «встроить» каррирование и композицию, но смотрится это так себе. Вместо композиции в C# широкое применение нашел pipelining:
Func<string, string> reverseWords = s => s.Words() .Select(StringExtensions.Reverse) .Unwords(); Pipelining, с которым мы работаем каждый день — это extension-методы для linq. На самом деле C# способен на большее и можно запрограммировать pipeline для любых входных и выходных аргументов с проверкой типов и поддержкой intellisense. Для разработки pipeline будут использоваться:
свойство nested-классов — возможность обращаться к приватным свойствам класса-родителя
var pipeline = Pipeline .Start(() => 10, x => x + 6) .Pipe(x => x.ToString()) .Pipe(int.Parse) .Pipe(x => Math.Sqrt(x)) .Pipe(x => x * 5) .Pipe(x => new Point((int) Math.Round(x), 120)) .Finish(x => Debug.WriteLine($"{x.X}{x.Y}")) .Do(() => Debug.WriteLine("Point is so cool")); // … pipeline.Execute(); Или так, применительно к CQRS и прикладному коду:
public class CreateBusinessEntity : ContextCommandBase<CreateBusinessEntityDto> { public CreateBusinessEntity(DbContext context) : base(context) {} public override int Execute(CreateBusinessEntityDto obj) => Pipeline .Pipe(obj, Map<CreateBusinessEntityDto, BusinessEntity>) .Pipe(SaveEntity) .Execute(); }
Для начала потребуется класс-контейнер, внутренний интерфейс для вызова функций и внешний — для реализации fluent interface:
public class Pipeline { private readonly object _firstArg; private object _arg; private readonly List<IInvokable> _steps = new List<IInvokable>(); private Pipeline(object firstArg) { _firstArg = firstArg; _arg = firstArg; } internal interface IInvokable { object Invoke(); } public object Execute() { _arg = _firstArg; foreach (IInvokable t in _steps) { _arg = t.Invoke(); } return _arg; } public abstract class StepBase { protected Pipeline Pipeline; public Step Do([NotNull] Action action) { if (action == null) throw new ArgumentNullException(nameof(action)); return new Step(Pipeline, action); } } }
И методы для создания pipeline:
public static Step Do(Action firstStep) { var p = new Pipeline(null); return new Step(p, firstStep); } public static Step<TInput, TOutput> Pipe<TInput, TOutput>( TInput firstArg, Func<TInput, TOutput> firstStep) { var p = new Pipeline(firstArg); // ReSharper disable once ObjectCreationAsStatement return new Step<TInput, TOutput>(p, firstStep); } public static Step<TInput, TOutput> Start<TInput, TOutput>( Func<TInput> firstArg, Func<TInput, TOutput> firstStep) { return Pipe(firstArg, x => x.Invoke()) .Pipe(firstStep); }
Теперь дело за реализациями шаблонов для fluent interface
public class Step : StepBase, IInvokable { private readonly Action _action; public Step(Pipeline pipeline, Action action) { Pipeline = pipeline; _action = action; Pipeline._steps.Add(this); } object IInvokable.Invoke() { _action.Invoke(); return Pipeline._arg; } public void Execute() => Pipeline.Execute(); } public class Step<TInput> : StepBase, IInvokable { private readonly Pipeline _pipe; private readonly Action<TInput> _action; public Step(Pipeline pipe, Action<TInput> action) { _pipe = pipe; _action = action; _pipe._steps.Add(this); } object IInvokable.Invoke() { _action.Invoke((TInput)_pipe._arg); return _pipe._arg; } public void Execute() => Pipeline.Execute(); } public class Step<TInput, TOutput> : StepBase, IInvokable { private readonly Pipeline _pipe; private readonly Func<TInput, TOutput> _func; internal Step(Pipeline pipe, Func<TInput, TOutput> func) { _pipe = pipe; _func = func; _pipe._steps.Add(this); } object IInvokable.Invoke() => _func.Invoke((TInput) _pipe._arg); public Step<TOutput, TNext> Pipe<TNext>([NotNull] Func<TOutput, TNext> func) { if (func == null) throw new ArgumentNullException(nameof(func)); return new Step<TOutput, TNext>(_pipe, func); } public Step<TOutput> Finish([NotNull] Action<TOutput> action) { if (action == null) throw new ArgumentNullException(nameof(action)); return new Step<TOutput>(Pipeline, action); } public TOutput Execute() => (TOutput)_pipe.Execute(); }
Шаблоны помогаю гарантировать, что в метод Pipe придет «правильный» аргумент. Отдельного внимания заслушивает метод Start, который позволяет передать в качестве аргумента не значение, а функцию:
var point = Pipeline .Start(() => 10, x => x + 6) .Pipe(x => x.ToString()) .Pipe(int.Parse) .Pipe(x => Math.Sqrt(x)) .Pipe(x => x * 5) .Pipe(x => new Point((int)Math.Round(x), 120)) .Execute();
Все некрасивые моменты, связанные с работой по ссылке на тип object мы спрятали внутрь сборки:
public object Execute() { _arg = _firstArg; foreach (IInvokable t in _steps) { _arg = t.Invoke(); } return _arg; }
Полный код доступен на github. Практическое применение:
объединение операций в логические цепочки и выполнение их в едином контексте (например, в транзакции)
Вместо Func и Action можно использовать Command и Queryи создавать цепочки вызовов
Также можно использовать Task и реализовывать фьючерсы для асинхронного программирования (не знаю на сколько это полезно, просто пришло в голову)
Warning: include(/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/bottom-cache.php): failed to open stream: No such file or directory in /volume1/web/cyberhost.biz/index.php on line 13 Call Stack: 0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0 Warning: include(): Failed opening '/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/bottom-cache.php' for inclusion (include_path='.:/usr/share/pear') in /volume1/web/cyberhost.biz/index.php on line 13 Call Stack: 0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0
Свежие комментарии