идея переключения / сопоставления с образцом - PullRequest
146 голосов
/ 01 октября 2008

Я недавно смотрел на F #, и, хотя я вряд ли скоро пройду через забор, он определенно выделяет некоторые области, где C # (или поддержка библиотеки) может облегчить жизнь.

В частности, я думаю о возможности сопоставления с образцом в F #, которая допускает очень богатый синтаксис - гораздо более выразительный, чем текущие переключатели / условные эквиваленты C #. Я не буду пытаться привести прямой пример (мой F # не подходит), но вкратце это позволяет:

  • соответствие по типу (с проверкой полного охвата для различающихся объединений) [обратите внимание, что это также выводит тип для связанной переменной, предоставляя доступ к члену и т. Д.)
  • совпадение по предикату
  • комбинаций вышеперечисленного (и, возможно, некоторых других сценариев, о которых я не знаю)

Хотя для C # было бы неплохо в конечном итоге позаимствовать [гм] часть этого богатства, в промежутке между тем я смотрел на то, что можно сделать во время выполнения - например, довольно легко собрать вместе несколько объектов, чтобы позволяют:

var getRentPrice = new Switch<Vehicle, int>()
        .Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
        .Case<Bicycle>(30) // returns a constant
        .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
        .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
        .ElseThrow(); // or could use a Default(...) terminator

где getRentPrice - это Func .

[примечание - возможно Switch / Case здесь неправильные термины ... но это показывает идею]

Для меня это намного яснее, чем эквивалент с использованием многократного if / else или составного троичного условного выражения (которое становится очень грязным для нетривиальных выражений - скобки в изобилии). Это также позволяет избежать много приведения и позволяет простое расширение (напрямую или с помощью методов расширения) до более конкретных совпадений, например, совпадение InRange (...), сопоставимое с VB Select .. .Case "x To y".

Я просто пытаюсь оценить, считают ли люди, что конструкции, подобные приведенным выше, приносят большую пользу (при отсутствии языковой поддержки)?

Обратите внимание, что я играл с 3 вариантами выше:

  • версия Func для оценки - сопоставимая с составными тройными условными выражениями
  • версия Action - сравнимо с if / else if / else if / else if / else
  • версия Expression > - как первая, но используемая произвольными поставщиками LINQ

Кроме того, использование версии на основе выражений позволяет переписывать дерево выражений, по существу объединяя все ветви в одно составное условное выражение, а не используя повторный вызов. Я недавно не проверял, но в некоторых ранних сборках Entity Framework я вспоминаю, что это было необходимо, так как ему не очень нравилось InvocationExpression. Он также позволяет более эффективно использовать LINQ-to-Objects, поскольку он избегает повторных вызовов делегатов - тесты показывают совпадение, подобное приведенному выше (с использованием формы выражения), с той же скоростью (на самом деле, немного быстрее) по сравнению с эквивалентным C # составное условное утверждение. Для полноты, основанная на Func <...> версия заняла в 4 раза больше времени, чем условный оператор C #, но все еще очень быстра и вряд ли станет основным узким местом в большинстве случаев использования.

Я приветствую любые мысли / замечания / критические замечания и т. Д. По поводу вышеизложенного (или о возможностях более богатой поддержки языка C # ... надеемся ;-p).

Ответы [ 11 ]

0 голосов
/ 13 сентября 2017

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

Основное преимущество перед switchif и exceptions as control flow) заключается в том, что он безопасен во время компиляции - нет обработчика по умолчанию или сбой

   OneOf<Motorcycle, Bicycle, Car> vehicle = ... //assign from one of those types
   var getRentPrice = vehicle
        .Match(
            bike => 100 + bike.Cylinders * 10, // "bike" here is typed as Motorcycle
            bike => 30, // returns a constant
            car => car.EngineType.Match(
                diesel => 220 + car.Doors * 20
                petrol => 200 + car.Doors * 20
            )
        );

Он на Nuget и нацелен на net451 и netstandard1.6

...