Стандарты кодирования 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))

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

См. Эта статья в блоге для получения дополнительной справочной информации.