[Из песочницы] Ещё один Pattern Matching на C#
Последнее время в процессе работы с языком C# я стал всё острее и острее нуждаться в механизме сопоставления с образцом, который присутствует во многих современных мультипарадигмальных языках (F#, Scala и т.д.), но отсутвует в C#. Найденые благодаря получасу гугления реализации (Пример) предлагали конструировать match-выражения посредством fluent-интерфейсов, что, на мой взгляд, довольно громоздко синтаксически. Вторым, уже более существенным для реального использования, недостатком является overhead на перебор предикатов в цикле, происходящий «под капотом» в таких мэтчерах. В общем, я задался целью написать собственную реализацию сопоставления с образцом, руководствуюсь двумя основными принципами: Синтаксически приблизится к «нормальным» конструкциям как в F#/Scala Приблизиться по производительности к коду с if/else if/else насколько это возможно За тем, что получилось — прошу под кат
Внешний вид Изначально мне хотелось сделать конструкцию мэтчера похожей на «настоящую», избежав цепочных вызовов методов. Решено было использовать список пар «предикат — функция». Для того, чтобы можно было использовать сокращённый синтаксис инициализации списка, класс Matcher реализует IEnumerable, а также имеет метод Add. Для удобства использования (например, для передачи в Select), в класс был добавлен метод неявного приведения к FuncВот как это выглядит при использовании:
Func
int len1 = match (null); // 0
int len2 = match («abc»); // 3
Реализация
Первая реализация, написанная в процессе поиска синтаксиса, была «наивной» и, так же как и найденые, производила поочерёдное выполнение предикатов с передаваемым в Match параметром. Когда код начал удовлетворять по первому пункту (быть внешне не громоздким), я переписал мэтчер с использованием Expression:
public class ExprMatcher
private Func
public void Add (Expression
private Func
var arg = Expression.Parameter (typeof (TIn)); var retVal = Expression.Label (typeof (TOut)); var matcher = Expression.Block ( Expression.Throw (Expression.Constant (new MatchException («Provided value was not matched with any case»))), Expression.Label (retVal, Expression.Constant (default (TOut))) ); foreach (var pair in reverted) { retVal = Expression.Label (typeof (TOut)); var condition = Expression.Invoke (pair.First, arg); var action = Expression.Return (retVal, Expression.Invoke (pair.Second, arg));
matcher = Expression.Block ( Expression.IfThenElse (condition, action, Expression.Return (retVal, matcher)), Expression.Label (retVal, Expression.Constant (default (TOut))) ); }
return Expression.Lambda
public TOut Match (TIn value) { return Matcher (value); }
public static implicit operator Func
public IEnumerator
IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } } При вызове метода Match () или при приведении к Func создаётся цепочное выражение, выбрасывающее MatchException в случае, если аргумент не удовлетворяет ни одному из предикатов. В результате получаем только оверхед в виде времени компиляции Expression.
Алгебраические типы
Другим неудобством использования C# для меня было отсутсвие типов-объединений в нём. Хотелось добавить их, но при этом сделать их использование настолько безопасным (на предмет NPE), насколько это возможно.Для начала было реализовано объединение двух типов:
public sealed class Union
Надеюсь, что мой мэтчер будет кому-нибудь полезен: Проект на bitbucketNuget пакет
Спасибо за внимание!