C# Обеспечение допустимых значений перечисления - метод будущего
Я пишу некоторый код с простым оператором 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
4 ответа
В таком случае я бы попытался использовать не enum, а класс с открытыми статическими полями только для чтения, которые являются экземплярами класса. Посмотрите, что.Net Framework делает с цветами, например. Существует класс Color, и вы можете использовать такие объекты, как Color.Black, Color.Blue и т. Д. Они не являются константами, но предлагают почти все те же преимущества. Плюс у них есть другие преимущества, которых нет у констант. См. Спецификацию языка C# версии 3, которая также немного говорит об этом.
Но идея в том, что у вас нет заявления по делу. Вы добавляете достаточно других свойств к каждому члену "enum", чтобы метод (DoSomething или любой другой) мог обрабатывать его правильно. Когда другой разработчик хочет добавить другой объект-член, он должен предоставить необходимые атрибуты. Мой пример: мне нужно было enum для различных действий, которые пользователь может выполнять в системе. Эти действия нужно было проверять на наличие разрешений, регистрировать и т. Д. Мне также требовались родительские и дочерние действия (переименование чего-либо является "частью" его редактирования и т. Д.), Абстрактные действия, используемые для группировки действий вместе для целей фильтрации, и специальные действия ". Все "и" Нет " (нет неопределенных). Каждому из них нужен был идентификатор и текст в базе данных. Я хотел, чтобы он все еще работал, если кто-то изобрел новый тип действия. Я сделал что-то вроде этого (много кода опущено, чтобы дать вам идею):
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. Он использует атрибуты действия.
Каково ваше перечисление?
С уважением
Я нашел "решение", используя простые Enums. Вот он, сырой, может быть, он может быть улучшен с помощью лучшего дизайна:
bool allCasesHandled;
switch (myEnumValue)
{
case MyEnum.Value1:
allCasesHandled = true;
break;
//default:
// allCasesHandled = true;
// break;
}
System.Diagnostics.Debug.WriteLine(allCasesHandled);
Если вы попытаетесь скомпилировать это, вы получите ошибку "использование неназначенной переменной".
Это немного громоздко в обслуживании, но для простых случаев это может быть полезно, особенно с комментарием к строке, которая предназначена для сбоя.
Я могу ошибаться, но я не думаю, что компилятор предлагает такие предупреждения. Вы можете обнаружить такие проблемы с некоторыми модульными тестами, которые вызывают метод, подобный приведенному выше, со всеми возможными значениями перечисления ( для этого используйте Enum.GetValues ()). Каждый раз, когда разработчик добавляет элемент enum и забывает изменить все операторы switch, по крайней мере, один модульный тест завершится с "StupidProgrammerException" (между прочим: я бы выбросил ArgumentOutOfRangeException).
Я думаю, что вы можете написать правила для StyleCop и запустить их в событии PostBuild, и они могут выводить предупреждения в окне Build. Мы начали пытаться добавить предупреждение для игнорирования возвращаемого значения метода, но так и не смогли его завершить. В последний раз, когда я смотрел на это, вам нужно было проанализировать IL, что не всегда весело. Конечно, я думаю, это зависит от вашего определения веселья.