Как бороться с перечислением 0 в C # (обсуждение CA1008) - PullRequest
0 голосов
/ 06 февраля 2019

Правило CA1008 указывает, что все перечисления должны иметь значение 0 с именем Unknown (здесь мы не обсуждаем флаги).Я понимаю причину, по которой вы хотите предотвратить то, чтобы неинициализированные значения автоматически приобретали смысл.Предположим, я определил следующее перечисление:

enum Gender
{
    Male,
    Female
}

class Person
{
    public string Name { get; set; } 
    public Gender Gender { get; set; }
}

Это указывает на то, что каждый человек должен быть либо мужчиной, либо женщиной (давайте пока не будем обсуждать гендерные вопросы).Если я забуду установить свойство Gender, то этот человек автоматически станет мужчиной, что может вызвать проблемы.По этой причине я понимаю предупреждение CA1008, поэтому значение 0 должно быть зарезервировано для неизвестного / неинициализированного значения.

Итак, давайте изменим перечисление Gender на значение 0 и больше не будем использовать:

enum Gender
{
    Male = 1,
    Female = 2
}

Когда я не указываю пол, тогда это не мужчина или женщина.Проблемы могут возникнуть во время сериализации.Значение 0 не очень наглядно для перечисления во время отладки.Чтобы исправить это и избежать предупреждения CA1008, я снова изменяю перечисление:

enum Gender
{
    Unknown = 0,
    Male = 1,
    Female = 2
}

Неинициализированные свойства теперь отображаются как Unknown, что выглядит хорошо.Но я, возможно, представил другую проблему, которая заключается в том, что значение Unknown выглядит как допустимое значение и может быть применено к пользователю.Я также мог бы получить предупреждения о том, что не имею дело со всеми значениями перечисления.Предположим, я использую конструктор, который требует, чтобы я указывал пол и имя, чтобы избежать неинициализированных свойств:

public Person(string name, Gender gender)
{
     Name = name ?? throw new ArgumentNullException(name);
     Gender = gender;
}

Когда я определяю перечисление Unknown, тогда я теперь могу явно установить пол в Unknown,Конечно, это можно проверить в конструкторе, но это будет сигнализироваться только во время выполнения.Если значение Unknown не определено, то вызывающая сторона может установить его только для мужчины или женщины.

Исправление может заключаться в использовании свойства пола, допускающего значение NULL, поэтому неинициализированное значение является явным nullзначение (мы больше не определяем значение Unknown).Но использование обнуляемых типов делает программирование более сложным, поэтому я бы не советовал.

Применение ObsoleteAttribute к значению Unknown может быть хорошей идеей.Когда кто-то явно использует значение, оно помечается как предупреждение (во время сборки).

Как правильно обращаться с неинициализированными значениями перечисления и использует ObsoleteAttribute - хорошая идея илиесть ли у него другие недостатки?

Примечания: * Хотя устаревшая здесь не является правильной семантикой, это единственный (простой) способ создания предупреждения, если используется значение.* Использование POCO без конструктора по умолчанию может усложнить сериализацию, поэтому вообще плохая идея иметь (сериализуемые) классы без них.

Ответы [ 3 ]

0 голосов
/ 07 февраля 2019

Ваш вопрос определяет три требования, которые, по моему мнению, в основном несовместимы друг с другом:

  1. Вы хотите, чтобы предупреждение CA1008 было исключено, если в перечислении указано значение по умолчанию, равное 0.
  2. Вы хотите, чтобы пользователь enum Gender не мог явно использовать неизвестное значение.
  3. Вы хотите реализовать конструктор по умолчанию для вашего класса Person.

Если выудовлетворяйте 1 и 2, тогда вы должны ввести конструктор не по умолчанию, чтобы обеспечить инициализацию Gender.Тогда 3 не может быть удовлетворено.

Если вы удовлетворяете 1 и 3, вы должны согласиться с тем, что пользователь может забыть инициализировать это свойство, и вы должны ввести что-то для обработки случая, когдаСвойство имеет допустимое значение, но не инициализировано.Во многих случаях решением было бы реализовать и обработать Неизвестное значение по умолчанию как третье допустимое значение, но тогда не может быть выполнено значение 2.

Если вы удовлетворяете 2 и 3, тогда вы сталкиваетесь с проблемой, в которой вы должнырешите, должен ли Пол быть инициализирован как Мужской или Женский по умолчанию, чтобы также удовлетворить # 1.Если используется конструктор по умолчанию, вы столкнетесь с проблемами, когда выбор по умолчанию будет неправильным в половине случаев.

Единственный путь вперед для удовлетворения всех трех из этих требований может заключаться в моделировании Gender как подтипа.of Person, а не просто свойство.

enum Gender
{
    Male,
    Female
}

abstract class Person
{
    public string Name { get; set; }
    public abstract Gender Gender { get; }
}

class MalePerson : Person
{
    public override Gender Gender { get { return Gender.Male; } }

    public MalePerson()
    { ... }
}

class FemalePerson : Person
{
    public override Gender Gender { get { return Gender.Female; } }

    public FemalePerson()
    { ... }
}

Таким образом, вы обязываете пользователя создавать экземпляр Person, используя конструктор по умолчанию Male или конструктор по умолчанию Female.Сериализация также сможет сохранять подтипы и использовать конструкторы по умолчанию, не приводя к неправильному значению по умолчанию.

0 голосов
/ 07 февраля 2019

хорошая идея использовать устаревший атрибут?

Нет.Используйте [Obsolete], чтобы ... ждать его ... пометить устаревший элемент как устаревший .Это только правильное использование [Obsolete].Не придумывайте новые значения для существующих слов;это просто создает путаницу.

Как правильно обращаться с неинициализированными значениями перечисления?

Это неправильный вопрос.Сделать шаг назад.Давайте посмотрим на более широкую картину вашего вопроса:

  1. Вы решили использовать перечисление
  2. Вы получили кучу предупреждений о том, что ваше перечисление нарушило некоторые рекомендации, и вы решили руководящие принципыбыли важны
  3. Каждая попытка следовать рекомендациям приводила к возникновению новой проблемы, и вы решили, что эти проблемы важны.

Теперь вы застряли, гадая, что делать.

Что вы делаете: возвращайтесь к шагам с первого по третий и принимайте различные решения .

Предположим, мы пересматриваем решение 3. Вы определили кучу плюсов и минусовкаждого решения.Решите, что для одного из них плюсы перевешивают минусы, и продолжайте с ним.

Предположим, мы пересматриваем решение 2. Вы нарушили кучу рекомендаций.Руководящие принципы не являются правилами.Вы можете решить, что руководящие принципы являются плохим советом для вашего сценария, задокументировать тот факт, что вы умышленно нарушаете их и почему, подавить предупреждение и пойти дальше.

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

abstract class Gender : 
   whatever interfaces you need for serialization and so on
{
  private Gender() { } // prevent subclassing 
  private class MaleGender : Gender 
  {
    // Serialization code for male gender
  }
  public static readonly Gender Male = new MaleGender();
  // now do it all again for FemaleGender
}

И что мы получили?У нас Gender.Male и Gender.Female такие же, как и раньше, они сериализуемы так же, как и раньше, и любое значение типа Gender является либо мужской, женский или нулевой.Не любите нули?Создайте исключение, как если бы вы получили пустую строку для имени человека.Хотите добавить больше полов, например, «Неизвестно», «Не двойные» или что-то еще?Добавить новые подтипы и статические поля.

Вы не обязаны использовать перечисление.Если вас раздражает соблюдение правил перечисления, прекратите использование перечислений.

0 голосов
/ 06 февраля 2019

Целью наличия перечисления является предоставление именованных констант, которые представляют возможные значения.В вашем конкретном дизайне (который является абстракцией реального мира) пол человека либо Male, либо Female.Там нет None.

Вашему перечислению нужен элемент значения 0, поскольку базовый тип по умолчанию - int.По этой причине (и, как указывает компилятор), это должен быть один из ваших вариантов (Male или Female):

public enum Gender
{
    Male, //compiler defaults to 0
    Female
}

или

public enum Gender
{
    Male = 0,
    Female = 1
}

или

public enum Gender
{
    Male = 1,
    Female = 0
}

Тот факт, что значение требуется, должен быть реализован конструктором вашей абстракции как личность должен иметь пол:

public Person(string name, Gender gender)
{
     Name = name ?? throw new ArgumentNullException(name);
     Gender = gender;
}

Еслис другой стороны, ваш дизайн абстрагирует мир, в котором человек может выбрать , а не , чтобы указать свой пол, тогда вы можете иметь значение по умолчанию, представляющее, что:

public enum Gender
{
    NotProvided,  //compiler defaults to 0
    Male,
    Female
}

или

public enum Gender
{
    NotProvided = 0
    Male = 1,
    Female = 2
}

В этом случае имеет смысл иметь два варианта конструкторов:

public Person(string name)
{
     Name = name ?? throw new ArgumentNullException(name);
}

public Person(string name, Gender gender)
{
     Name = name ?? throw new ArgumentNullException(name);
     Gender = gender;
}

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

...