Еще Одно Сопоставление С Образцом В C# — Теперь С Построением Контекста

Полтора месяца назад я опубликовал статья , посвященный реализации сопоставления шаблонов в C#.

В Комментарии к статье gBear справедливо отметил отсутствие контекста в приведенных случаях.

В первой версии сопоставителя я сознательно игнорировал этот механизм, так как считал синтаксические возможности выражений в C# недостаточными для его реализации.

Однако спустя некоторое время я понял, что желаемого эффекта можно добиться, построив Выражение вручную.

Ниже приведена реализация полноценного сопоставления с образцом.

Изначально, при реализации сопоставления с образцом, я хотел сделать синтаксис выражения case похожим на следующий:

  
  
  
  
  
  
  
   

s => string.IsNullOrEmpty(s) => 0

К сожалению, в C# это синтаксически неверно: по сути

s => t => s * t

Представляет функцию в каррированной форме.

Вторая идея для выражения case заключалась в использовании тернарного оператора, подобного следующему:

s => t ? a : b

Что опять же невозможно из-за отсутствия типа Unit в C# (для использования в ветке else).

Была идея сделать тип для выражения b Expression<.

> и передать туда следующий случай, но этому препятствует требование, чтобы типы выражений a и b были идентичными.

В какой-то момент я свыкся с мыслью, что не смогу реализовать контекстную связность и использовал сопоставление в том виде, в котором оно существует. Однажды во время отладки кода, подобного следующему

.

{s => s is string, s => ((string)s).

Length} .



Я подумал, что вместо проверки типа is можно выполнить преобразование as и проверить результат этого преобразования.

Тут меня осенило – ведь это по сути будет построение контекста! Не долго откладывая, я приступил к его реализации.

Во второй версии было решено полностью отказаться от реализации Matcher путем перечисления лямбда-функций и использовать только деревья выражений (как в ExprMatcher).

Метод Add необходимо было ввести:

public void Add<TCtx>(Expression<Func<TIn, TCtx>> binder, Expression<Func<TCtx, TOut>> processor) { var bindResult = Expression.Variable(typeof (TCtx), "binded"); var caseExpr = Expression.Block( new []{bindResult}, Expression.Assign(bindResult, Expression.Invoke(binder, Parameter)), Expression.IfThen( Expression.NotEqual(Expression.Convert(bindResult, typeof(object)), Expression.Constant(null)), Expression.Return(RetPoint, Expression.Invoke(processor, bindResult)) )); _caseExpressionsList.Add(caseExpr); }

Тип TCtx является «типом контекста» для данного случая.

Если первое выражение возвращает ненулевой экземпляр TCtx, выполняется второе выражение, и его аргумент является результатом сравнения.

Было решено оставить предыдущий синтаксис с предикатами, потому что иногда это удобнее:

public void Add(Expression<Predicate<T>> condition, Expression<Action<T>> processor) { var caseExpr = Expression.Block( new Expression[]{ Expression.IfThen( Expression.Invoke(condition, Parameter), Expression.Return(RetPoint, Expression.Invoke(processor, Parameter)) )}); _caseExpressionsList.Add(caseExpr); }

Поскольку выражения для случаев теперь строятся непосредственно при добавлении, код для сборки выражения полного сопоставления был значительно упрощен:

private Func<TIn, TOut> CompileMatcher() { var finalExpressions = new Expression[] { Expression.Throw(Expression.Constant(new MatchException("Provided value was not matched with any case"))), Expression.Label(RetPoint, Expression.Default(typeof(TOut))) }; var matcherExpression = Expression.Block(_caseExpressionsList.Concat(finalExpressions)); return Expression.Lambda<Func<TIn, TOut>>(matcherExpression, Parameter).

Compile(); }

Приведу небольшой пример функции, которая возвращает значение аргумента, если его тип — строка или StringBuilder, и строку «Неизвестный объект» — в противном случае:

var match = new Matcher<object, string> { {s => s as string, s => s}, {sb => sb as StringBuilder, sb => sb.ToString()}, {o => true, (bool _) => "Unknown object"} }.

ToFunc();

В планах на будущее добавление в пакет набора готовых дискриминаторов для распространённых случаев.

Пожалуй, это все.

На всякий случай приведу ссылки на проект и nuget-пакет матчера: Проект на битбакете Пакет Nuget Как и в прошлый раз, буду рад замечаниям/предложениям в комментариях.

Спасибо за внимание! Теги: #C++ #сопоставление с образцом #C++

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.