Полтора месяца назад я опубликовал статья , посвященный реализации сопоставления шаблонов в C#.
В Комментарии к статье gBear справедливо отметил отсутствие контекста в приведенных случаях.
В первой версии сопоставителя я сознательно игнорировал этот механизм, так как считал синтаксические возможности выражений в C# недостаточными для его реализации.
Однако спустя некоторое время я понял, что желаемого эффекта можно добиться, построив Выражение вручную.
Ниже приведена реализация полноценного сопоставления с образцом.
Изначально, при реализации сопоставления с образцом, я хотел сделать синтаксис выражения case похожим на следующий:
К сожалению, в C# это синтаксически неверно: по сутиs => string.IsNullOrEmpty(s) => 0
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++
-
Новые Команды И Инструменты В Alibre Design
19 Oct, 24 -
Вы Становитесь Интернет-Зависимым??
19 Oct, 24 -
Менеджер Загрузки Для 486-Го Компьютера
19 Oct, 24