С# Обеспечить действительные значения перечисления - метод будущей защиты

Я пишу код с простым оператором switch, основанным на значениях Enum. Мне пришло в голову, что в какой-то момент в будущем разработчик может добавить новое значение, поэтому я включил метод по умолчанию, чтобы зафиксировать это во время выполнения и выбросить исключение. Однако я понял, что я должен делать это каждый раз, когда я вставляю логику, и что я буду видеть такие проблемы во время выполнения, а не компилировать время.
Я задаюсь вопросом, есть ли какой-нибудь код, который я могу добавить, чтобы заставить компилятор сказать разработчику, что им необходимо обновить определенные методы в том случае, если они обновляют значения перечисления - помимо добавления комментариев к самому перечислению?

например. (пример ниже чисто теоретический, я выбрал статусы из жизненного цикла разработки, чтобы обеспечить ему что-то знакомое).

public enum DevelopmentStatusEnum
{
    Development
    //, QA //this may be added at some point in the future (or any other status could be)
    , SIT
    , UAT
    , Production
}

    public class Example
    {
        public void ExampleMethod(DevelopmentStatusEnum status)
        {
            switch (status)
            {
                case DevelopmentStatusEnum.Development: DoSomething(); break;
                case DevelopmentStatusEnum.SIT: DoSomething(); break;
                case DevelopmentStatusEnum.UAT: DoSomething(); break;
                case DevelopmentStatusEnum.Production: DoSomething(); break;
                default: throw new StupidProgrammerException(); //I'd like the compiler to ensure that this line never runs, even if a programmer edits the values available to the enum, alerting the program to add a new case statement for the new enum value
            }
        }
        public void DoSomething() { }
    }
    public class StupidProgrammerException: InvalidOperationException { }

Это немного академический, но я вижу, что это полезно для того, чтобы сделать мое приложение надежным. Кто-нибудь пробовал это раньше/получил какие-либо хорошие идеи о том, как это может быть достигнуто?

Спасибо заранее,

JB

Ответ 1

В таком случае я постараюсь не использовать перечисление, а скорее класс с общедоступными статическими полями readonly, которые являются экземплярами класса. Посмотрите, что делает инфраструктура .Net с цветами, например. Существует класс Color, и вы можете использовать такие объекты, как Color.Black, Color.Blue и т.д. Они не являются константами, но предлагают почти все те же преимущества. Кроме того, у них есть другие преимущества, которых нет у констант. См. Спецификацию языка С# версии 3, которая также немного об этом говорит.

Но идея в том, что у вас нет аргумента case. Вы добавляете достаточно других свойств каждому члену "enum", чтобы метод (DoSomething или что-то еще) мог правильно его обработать. Когда другой разработчик хочет добавить еще один объект-член, ему необходимо предоставить необходимые атрибуты. Мой пример: мне понадобилось "перечисление" для различных действий, которые пользователь может принять в системе. Эти действия нужно было проверять на наличие разрешений, регистрироваться и т.д. Мне также нужны действия родителя и ребенка (переименование чего-то является "частью" его редактирования и т.д.), Абстрактные действия, используемые для группировки действий для целей фильтрации, и специальные действия "Все" и "Нет" (None undefined). Каждому требовался идентификатор и текст в базе данных. Я хотел, чтобы он по-прежнему работал, если кто-то изобрел новый тип действия. То, что я сделал, это что-то вроде этого (много кода опущено только для того, чтобы дать вам идею):

  public class Action
  {
    protected Action(bool Abstract, Action Parent, int ID, string Name, bool Undefined)
    { /* snip */ }
    protected Action(bool Abstract, Action Parent, int ID, string Name)
      : this(Abstract, Parent, ID, Name, false)
    { }
    //----------------------------------------------------------------------------------------
    public static readonly Action All = new Action(true, null, 0, "All");
    public static readonly Action None = new Action(false, All, 6, "(Undefined)", true);
    public static readonly Action Modifying = new Action(true, All, 1, "Modifying");
    public static readonly Action Creating = new Action(false, Modifying, 2, "Creating");
    public static readonly Action Deleting = new Action(false, Modifying, 3, "Deleting");
    public static readonly Action Editing = new Action(false, Modifying, 4, "Editing");
    public static readonly Action Exporting = new Action(false, All, 5, "Exporting");
    public static readonly Action Renaming = new Action(false, Editing, 7, "Renaming");
    /* snip */
    //----------------------------------------------------------------------------------------
    /* template for new entries:
    public static readonly Action  = new Action(false, All, , "");
    */
  }

Есть больше действий. И есть ряд методов в других классах, которые действуют на действия. Они все продолжают работать, пока каждое действие предоставляет необходимую информацию. Разработчик, добавляющий действие, вынужден предоставить информацию. Если текущие атрибуты недостаточны для какого-то будущего "специального" действия, тогда дополнительные атрибуты нужно будет добавить позже. Обратите внимание, что конструкторы защищены, поэтому только сам класс может создавать действия. Я пропустил много кода в главном конструкторе, который проверяет наличие дубликатов идентификаторов и имен и многое другое. Теперь вы можете использовать его следующим образом:

Log.LogAction(Action.Renaming);

В методе LogAction нет аргумента case. Он использует атрибуты действия.

Каково ваше перечисление?

Привет

Ответ 2

Я нашел "решение" с использованием простых Enums. Вот он, сырой, возможно, его можно улучшить с лучшей конструкцией:

bool allCasesHandled;

switch (myEnumValue)
{
    case MyEnum.Value1:
        allCasesHandled = true;
        break;

    //default:
    //  allCasesHandled = true;
    //  break;
}
System.Diagnostics.Debug.WriteLine(allCasesHandled);

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

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

Ответ 3

Возможно, я ошибаюсь, но я не думаю, что компилятор предлагает такие предупреждения. Вы можете поймать такие проблемы с помощью некоторых модульных тестов, которые вызывают метод, подобный описанному выше, со всеми возможными значениями перечисления (используйте Enum.GetValues ​​() для этого). Каждый раз разработчик добавляет член перечисления и забывает модифицировать все инструкции switch, по крайней мере один unit test завершит сбой с "StupidProgrammerException" (btw: я бы выбрал исключение ArgumentOutOfRangeException).

Ответ 4

Я думаю, что вы можете писать правила для StyleCop и запускать их в событии PostBuild, и они могут выводить предупреждения в окне сборки. Мы начали эту попытку некоторое время назад, добавив предупреждение для игнорирования возвращаемого значения метода, но так и не добравшись до его завершения. Последнее, что я просмотрел, вам нужно было проанализировать IL, что не всегда весело. Конечно, я думаю, это зависит от вашего определения веселья.