В прошлый раз я наглядно рассказывал тебе, мой друг, что такое методы расширения и как их можно использовать. В этот раз я постараюсь более конкретней подвести тебя к сути. Тема эта сложная и требует глубоких знаний языка, но думаю для тебя это не проблема, надеюсь ты так же как и я стремишься к новым знаниям.
Сама по себе, эта тема мало интереса представляет для читерства как такового, но для повышения уровня кода и знаний это будет полезно. Как можно повысить уровень кода? Один из принципов ООП гласит, что нужно исключать дублирование кода, я выбрал именно этот принцип, потому что это самая популярная проблема языка на мой взгляд. Вторая проблема которую я выделю - лаконичность кода, он должен быть легко читаемым и понятным по смыслу. Сложно представить что C# не лаконичен по своей сути, его оказуалили так, что на нем легко сможет писать и ребенок, но все же есть некоторые места которые меня очень раздражают.
Начнем с дублирования. Нам дали полиморфизм, что бы мы писали логику в одном классе и использовали её в других без дублирования. Нам дали дженерики, что бы мы могли отвязать логику от конкретных типов и привязать её хоть ко всем сразу. Но знаете, дублирования какого кода полно в куче исходников и от которого не так-то просто избавиться? Приведу небольшой пример:
Код:
void Foo( object argument )
{
//todo
}
Допустим, этот метод находится в очень критичной отказоустойчивой системе и нам нужно произвести какие-то действия с аргументом. Нужно ли объяснять что необходимо проверить инициализацию аргумента? Тем более если аргумент приходит от какого-то внешнего пользовательского кода. Что бы обратиться к аргументу, сначала нужнно сделать такую проверку:
Код:
If ( argument == null ) return;
Обычно, если аргумент неинициализирован, то вызывается ошибка, но мы помним что код находится в важной системе и максимум можем написать в лог о том, что аргумент невалидный, либо как-нибудь по другому оповестить об этом. Только в 2015 году, в версии 6 языка C# предложили оператор ".?" который сокращает дублирование данных проверок. При чем в нашем случаее этот оператор никак не поможет, а таких проверок куча. И только в следующей версии языка разработчики планируют ввести обязательные аргументы которые никогда не равны null, но это уже другая история.
На самом деле, все это меня не натолкнуло на поиск какого-либо решения. Эти вещи незначительны и с ними можно прижиться. Как-то раз я увидел в интернетах про монаду Maybe на языке C#, конкретная реализация меня не впечатлила, но вот возможность языка комбинировать методы расширения с дженериками рождая при этом что-то вроде монад, очень даже. Я начал с этим экспериментировать, максимально обобщая монады, заменяя некоторые конструкции языка и получилось кое-что, о чем я и буду писать в этом цикле статей.
Итак, что же такое монады, описание из вики:
Цитата:
Мона́да в функциональном программировании — это абстракция линейной цепочки связанных вычислений. Её основное назначение — инкапсуляция функций с побочным эффектом от чистых функций, а точнее их выполнений от вычислений. Монады применяются в языке Haskell, так как он повсеместно использует ленивые вычисления, которые вместе с побочным эффектом, как правило, образуют плохо прогнозируемый результат.
Мы возьмем за основу это понятие и будем развивать в C#. Наши монады будут возвращать предсказуемый результат, по крайней мере мы снабдим их этой возможностью. Монады будут завязаны на методах расширения, принимать в себя один аргумент в виде дженерика и еще один аргумент в виде действия над ним. Действием будет являться один из трех стандартных делегатов: [Ссылки могут видеть только зарегистрированные пользователи. ], [Ссылки могут видеть только зарегистрированные пользователи. ], [Ссылки могут видеть только зарегистрированные пользователи. ].
Развивая эту идею, появилось правило взаимодействия монад. Есть одна основная монада, другие монады работают с ней, самостоятельно они должны делать по минимуму, это нужно для концепции в целом и для решения первой проблемы с дублированием кода в частности.
Итак, наша первая монада - Do. Она делает что-то с вашим аргументом ( или не делает, по ситуации ), а потом возвращает этот же аргумент, что бы можно было делать цепочку таких выражений в функциональном стиле.
Код:
public static T Do<T>( this T argument, Action<T> doAction )
{
If ( argument != null ) doAction?.Invoke ( argument );
return argument;
}
Так как действие вызывается через Action, мы не можем попросить монаду посчитать нам что-то или изменить исходный аргумент. Что бы это можно было сделать, мы добавим в качестве действия функтор.
Обратите внимание на реализацию с использованием default значения, это нужно для структур, если с классами этого можно избежать и просто вернуть null, а во внешнем коде использовав оператор "??" присвоить какое-то значение, то со структурами так просто не выйдет и мы даем возможность определить значение выходного результата заранее, если выполнить выражение не получается. Теперь мы можем легко изменять нашу переменную по монадической цепочке:
Код:
"Hello".Do ( WriteLine )
.Do ( s => s += " Do" ).Do ( WriteLine )
.Do ( s => s.Replace ( "Do", "World" ) ).Do ( WriteLine );
Этих двух методов будет достаточно для монады Do, что бы в дальнейшем её использовали и другие монады, а о них продолжу в следующих частях.
using System;
namespace Monads
{
public static class DoMonad
{
public static T Do<T>( this T argument, Action<T> doAction )
{
if ( argument != null ) doAction?.Invoke ( argument );
return argument;
}
public static TResult Do<T, TResult>( this T argument, Func<T,TResult> doFunc,
TResult defaultResult = default(TResult) )
{
return argument == null && doFunc == null ? defaultResult : doFunc ( argument );
}
}
}
________________
code safe my friend Для просмотра ссылок или изображений в подписях, у Вас должно быть не менее 10 сообщение(ий). Сейчас у Вас 0 сообщение(ий).
Последний раз редактировалось HideFebruary; 06.03.2016 в 14:57.