
Комментарии 19
Так, а что же не так то с enum и switch? Хорошо бы пример привести, где не так, и потом, где все стало так. А то вы простой и понятный пример со switch превратили в кучу непонятного кода с интерфейсами.
З.Ы. Я, понимаю зачем все это (не уверен, правда в самом решении, так как C# не знаю), но думаю, многим будет непонятно.
Инспекции решарпера бесплатны, если запускать из комманд лайна на билд сервере.
У решарпера есть правило о том, что switch должен быть исчерпывающим
Но ведь если есть default throw, как в примере на картине, это в глазах линтера разве не исчерпывающий switch? С другой стороны, раз там enum, то лучше без default обходиться в таких местах.
При добавлении нового значения в enum необходимо:
Добавить метод в интерфейс.
Добавить его использование в метод расширение.
Просто хотел добавить, что такое решение подойдет, если все или почти все значения enum нужно обработать не дефолтным способом. А если нужно обработать только малую часть enum, то решение со switch будет лучше, т.к. будет меньше boilerplate кода.
Что же тогда надо будет городить если у вас flag enum...
Вам, на самом деле не нужны эти костыли, ReSharper или что-то еще.
Все гораздо проще
enum MyEnum { A, B, C }var e = (MyEnum)new Random().Next(0, 2);
Console.WriteLine(e switch
{
MyEnum.A => "A",
MyEnum.B => "B"
});Здесь появляется warning
CS8509 The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'ConsoleApp1.Program.MyEnum.C' is not coveredВ .editorconfig'е переводите этот warning в ошибку.
Профит!
Часто ли у вас было такое, что вы добавляли новое значение в enum и потом тратили часы на то, чтобы найти все места его использования, а затем добавить новый case, чтобы не получить ArgumentOutOfRangeException во время исполнения?
Никогда не приходилось тратить на это часы. Давишь Shift+F12 и пробегаешься по списку, правишь, где что надо.
А о чем статья? Этому паттерну уже триста лет.
Я не уверен, что эту реализацию можно называть посетителем. Мотивация посетителя в том, чтобы выбирать алгоритм поведения в зависимости от типа объекта которого он посещает. Достигается это за счет того, что посещаемый объект сам выбирает метод, который может его обработать. В данном же случае вы ориентируетесь по значению.
Возможно вам стоит приложить усилия не к уменьшению последствия проблемы, а решить саму проблему? Я предлагаю вам попытаться избавиться от свичей. К сожалению я не могу вам предложить серебряную пулю. Решение придётся искать своё в каждом конкретном случае. Но возможно вам следует присмотреться к паттерну состояние.
Если нужно при добавлении нового элемента добавлять его обработку в разные методы — возможно вы неправильно декомпозировали задачу и нужно превратить элемент в интерфейс, который будет иметь реализации для разных типов
Тогда останется только один свич — при создании типа объектов
До чего же бедный язык C#, раз приходится в нём громыхать связанными идентификаторами (Invoice — VisitInvoice) и создавать ошибкоопасные порядковые аргументы функции Match?
enum MyEnum {
Value1,
Value2,
Value25, // добавили
Value3
}
static T Match<T>(this MyEnum self,
Func<T> on1,
Func<T> on25, // добавили не туда
Func<T> on2,
Func<T> on3) { ..... }
.....
var answer = my_enum.Match(
() => "one",
() => "two",
() => "three",
() => "two and half" // добавили не туда
);Вы спросите, как такие детские ошибки можно вообще допустить?
Да как нефиг делать. Рефакторинг, мать его.
1) Добавили Value25 в конец энума, в конец функции, в конец всех вызовов функции
2) Потом переместили в энуме и функции (естественно, проворонили использование — сигнатура-то не поменялась)
3) А потом криво автоматически смёржили в системе контроля версий. И вот уже не только использования, но и сам энум с функцией разъехались.
Есть и ещё один в меру костыльный вариант, но который не требует наличия того же решарпера. По сути, вся задача сводится к тому, чтобы при добавлении нового элемента в enum, switch кидал ошибку при компиляции. Добиться этого можно, добавив дополнительное значение в enum и проверку времени компиляции в switch. Выглядит примерно так:
- Есть enum:
- enum Test { - A - } - Есть switch с элементами enum:
- Test value = Test.A; - switch (value) { - case Test.A: - ... - break; - default: - throw new ArgumentOutOfRangeException(); - }
3.1. Добавляем в enum элемент
Last:
- enum Test { - A, - Last - }
В нашем случае элемент Last — число 1, т.к. по умолчанию члены enum — константные целые числа от 0 по возрастанию.
3.2. Добавим специальный код в switch:
- Test value = Test.A; - switch (value) { - case Test.A: - ... - break; - case Test.Last: - const int _ = 1 / (1 / (int) Test.Last) - throw new ArgumentException(); - default: - throw new ArgumentOutOfRangeException(); - }
Выражение
1 / (1 / (int) Test.Last)будет вычислено во время компиляции. В случае, если Last будет больше второго числа (1 в скобках), то второе частное будет равно 0, что приведёт к делению на 0 во время компиляции и выкинет ошибку. Как только решили добавить новое значение в enum, его нужно добавить перед Last, чтобы значение Last увеличилось.
Конечно же здесь есть недостатки:
- Добавляется ещё одна проверка в switch — overhead в runtime
- Можно применять только в случае, когда элементы в enum — возрастающие числа, а
Last— последний элемент - Нужно помнить, зачем в enum элемент
Last
Но тут уже нужно самим решить, стоит оно того или нет в конкретном случае.
P.S. если кто-то знает, как отключить в markdown хабра удаление пробелов в начале строки, welcome
Enum и switch, и что с ними не так