Стандарты кодирования Lance Hunt С# - путаница перечисления
Моя команда недавно начала использовать документ Lance Hunt С# Standards Standards в качестве отправной точки для консолидации наших стандартов кодирования.
Есть один предмет, который мы просто не понимаем, может ли кто-нибудь пролить свет на него?
Элемент номер 77:
Всегда проверяйте перечисление значение переменной или параметра перед потребляя его. Они могут содержать любые значение, которое соответствует типу Enum (по умолчанию int).
Пример:
public void Test(BookCategory cat)
{
if (Enum.IsDefined(typeof(BookCategory), cat))
{…}
}
Ответы
Ответ 1
Я думаю, что комментарии выше в значительной степени отвечали на вопрос. По сути, когда я написал это правило, я пытался передать защитную кодирующую практику проверки всех входных данных. Enums - особый случай, потому что многие разработчики неправильно предполагают, что они проверяются, когда они не являются. В результате вы часто увидите, не вызывают ли утверждения или инструкции switch для значения перечисления undefined.
Просто имейте в виду, что по умолчанию перечисление представляет собой не что иное, как оболочку вокруг INT, и проверяет ее так же, как если бы это был int.
Для более подробного обсуждения правильного использования перечислений вы можете просмотреть записи в блогах Брэда Абрамса и Кшиштофа Квалины или их отличную книгу "Руководство по разработке рамок: условные обозначения, идиомы и шаблоны для многоразовых библиотек .NET"
Ответ 2
Дело в том, что вы можете надеяться, что, имея параметр типа BookCategory, у вас всегда будет значимая категория книг. Это не так. Я мог бы позвонить:
BookCategory weirdCategory = (BookCategory) 123456;
Test(weirdCategory);
Если перечисление предназначено для представления известного набора значений, нельзя ожидать, что код не будет разумно обрабатывать значение за пределами этого хорошо известного набора. Тест проверяет, подходит ли аргумент в первую очередь.
Я бы лично изменил логику:
public void Test(BookCategory cat)
{
if (!Enum.IsDefined(typeof(BookCategory), cat))
{
throw new ArgumentOutOfRangeException("cat");
}
}
В С# 3 это легко сделать с помощью метода расширения:
// Can't constrain T to be an enum, unfortunately. This will have to do :)
public static void ThrowIfNotDefined<T>(this T value, string name) where T : struct
{
if (!Enum.IsDefined(typeof(T), value))
{
throw new ArgumentOutOfRangeException(name);
}
}
Использование:
public void Test(BookCategory cat)
{
cat.ThrowIfNotDefined("cat");
}
Ответ 3
Перечисления не отмечены:
enum Foo { A= 1, B = 2, C = 3}
Foo x = (Foo) 27; // works fine
Теперь у вас есть Foo
, который не определен.
Он просто говорит "проверьте свои данные". Заметим, что Enum.IsDefined
относительно медленный (на основе отражения). Лично я склонен использовать переключатель с default
, который генерирует исключение:
switch(x) {
case Foo.A: /* do something */ break;
case Foo.B: /* do something */ break;
case Foo.C: /* do something */ break;
default: throw new ArgumentOutOfRangeException("x");
}
Ответ 4
Это потому, что полностью законно называть этот метод "Test" следующим образом:
Test((BookCategory)-999);
Это будет использовать -999 как BookCategory, но, конечно, результат (переданный как параметр "cat" в Test) не является допустимым значением для перечисления BookCategory.
Ответ 5
Если вы хотите расширить свой enum новыми значениями в будущих версиях вашей библиотеки, не следует использовать конструкцию
if (Enum.IsDefined(typeof (BookCategory), cat))
Поскольку это только проверяет, является ли тип действительным в соответствии с определением перечисления. С другой стороны, ваш код был протестирован только для допустимых значений и может не работать для новых значений.
См. Эта статья в блоге для получения дополнительной справочной информации.