Как я могу сделать что-то вроде динамического перечисления в C #?

2

В приложении, которое я создаю, я имел перечисление статусов учетной записи:

 public enum AccountStatus
 {
      Active = 1,
      Trial = 2,
      Canceled = 3
 }

Однако мне понадобилась дополнительная информация из AccountStatus, поэтому я создал класс, который имеет несколько дополнительных полезных свойств:

 public class AccountStatus
 {
      public int Id {get; set;}
      public string Description {get; set;}
      public bool IsActive {get; set;}
      public bool CanReactivate {get; set;}
 }

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

 1,  "Active",       True,  True
 2,  "Trial",        True,  True 
 3,  "ExpiredTrial", False, True
 4,  "Expelled",     False, False

Это действительно удобно, когда у меня есть объект клиента, который использует AccountStatus, потому что я могу написать код вроде:

 if(customer.Status.CanReactivate) // Show reactivation form

Однако я потерял что-то не менее важное. Я больше не могу этого делать:

 if(customer.Status == AccountStatus.Active)  // allow some stuff to happen

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

 public static readonly AccountStatus Active = new AccountStatus(1);
 public static readonly AccountStatus Trial = new AccountStatus(2);
 // etc, etc ...

Я предполагаю, что, вероятно, есть образец для этого где-то, я просто не знаю, как его называют.

Любые идеи?

РАЗЪЯСНЕНИЕ

На основании ответов до сих пор мне нужно прояснить пару вещей.

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

Во-вторых, я дал очень простой пример использования утраченной способности, которая, по-видимому, смутила вопросы. Вот еще один реальный пример:

 if(customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial)

... ни Trial, ни ExpiredTrial не являются логическими значениями свойства. Я тоже не хочу их добавлять. Это установило бы еще худший прецедент, чем тот, который я пытаюсь избежать (это означает, что мне придется добавлять новое свойство в класс каждый раз, когда я добавляю новую запись в таблицу).

UPDATE

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

  • 0
    Я не совсем понимаю ... Можете ли вы объяснить, что вы подразумеваете под этим: "в конечном итоге это не работает, потому что если база данных изменится, код придется обновлять вручную."?
  • 0
    Как вы могли бы написать код, используя эти данные статически, и в то же время сохранить динамику удаления / добавления записей в БД. Вы не можете без изменения вашего кода C #, если вы измените свой код БД. Или вам следует прибегнуть к использованию других методов, описанных некоторыми из приведенных ниже плакатов, например: == new AccountStatus ("Active")
Теги:
design-patterns
enums

10 ответов

4
Лучший ответ

Но почему вы не можете использовать перечисление как свойство этого класса..?

public enum State
{
    Active = 1,
    Trial = 2,
    Canceled = 3
}

public class AccountStatus
{
    public int Id {get; set;}
    public State State {get; set;}
    public string Description {get; set;}
    public bool IsActive {get; set;}
    public bool CanReactivate {get; set;}
}

И затем:

if(customer.Status == AccountStatus.State.Active)  // allow some stuff to happen
  • 0
    Это примерно тот же результат, что и при использовании полей только для чтения. Когда это становится проблемой, через несколько месяцев другой разработчик может увидеть таблицу со значениями AccountStatus и сказать себе: «Ах! Все, что мне нужно сделать, это добавить новую запись здесь, и моя работа выполнена!» Не говоря уже о нарушении принципа СУХОЙ.
  • 0
    @ Парусный спорт, я не понимаю. Если разработчик добавляет новую строку AccountStatus, он, конечно, должен добавить код для работы с ним. Обойти это невозможно. С другой стороны, ни одно из этих решений не сломается, если будет добавлена строка - они просто не распознают ее, пока она не будет добавлена в код.
Показать ещё 3 комментария
2

Вместо того, чтобы работать с сильно типизированным enum, вы могли бы просто выполнять сравнения с помощью строки:

public static readonly AccountStatus Active = new AccountStatus("Active");

или загрузите тип из своей базы данных:

public static readonly AccountStatus Trial = new AccountStatus( reader["StatusField"] );

Затем вы можете делать явные сравнения:

if(customer.Status == "Active")

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

изменить

Вы могли бы, конечно, сделать это, используя соответствующие значения целого числа, как вы намекали в конце своего сообщения. Но строки легче читать, и в этом случае использование целых чисел не дает каких-либо типизирующих преимуществ.

  • 0
    Я не -1 это ... Я действительно хотел бы, чтобы ppl прокомментировал, когда они пометят ответ. Тем не менее, вы правы. Я использовал int в конструкторе моего примера кода. Это ужасно, что я сделал это, но это был побочный эффект отсутствия перечислений. Строки читаются лучше, но так же плохо. Я собираюсь +1 это просто, чтобы компенсировать необоснованный (или, по крайней мере, необъяснимый) минус.
1

Я по-прежнему считаю, что лучше всего добавить свои недостающие случаи в класс.

 public class AccountStatus
 {
      public int Id {get; set;}
      public string Description {get; set;}
      public bool IsActive {get; set;}
      public bool CanReactivate {get; set;}
      public bool Trial {get; set;}
      public bool ExpiredTrial {get; set;}
 }

Что вы можете назвать в более простой форме, чем ваш пример:

if(customer.AccountStatus.Trial || customer.AccountStatus.ExpiredTrial)

Если вам нужно проверить статус UserDefined, выведите его как отдельное свойство:

public AccountStatusCode Status  {get; set;}

... и назовите его следующим образом:

if(customer.Status == AccountStatus.Active)

Вы можете добавить к нему конструктор, если хотите установить начальный статус.

  • 0
    Извините, не понял, что Павел уже решил это.
  • 0
    Тем не менее, полезный ответ ... спасибо.
1

Я думаю, вы могли бы добиться этого, используя перечисление Flags, где вы можете комбинировать значения:

[Flags]
public enum AccountStatus
{
    Expelled = 1,
    Active = 2,
    CanReactivate = 4,
    Canceled = 8,
    Trial = Active | CanReactivate,
    ExpiredTrial = CanReactivate,        
}

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

  • 0
    Интересно, я не думал об использовании флагов, которые могли бы решить ситуацию в некоторых случаях. Тем не менее, это может запутаться, если я решу добавить еще один тип статуса, или, что еще хуже, еще одно «свойство».
1

если вы делаете/хотите что-то подобное в своем приложении:

if (customer.Status == AccountStatus.Active)

Вы должны знать в своем коде, что "Активный" - это возможный статус. Как еще вы могли бы написать фактическое слово Active в своем коде. Объект состояния может быть динамическим, но остальная часть программы, использующая статус, должна знать, какие типы состояний существуют, чтобы сделать что-то полезное с ней. Что делать, если активный объект больше не существует, объект статуса может не нуждаться в повторном выполнении, но используемый код.

Если статус каждого типа полностью определен такими параметрами, как кажется почти (активные и трейлы имеют одинаковые параметры, поэтому для дифференциации (даты истечения срока действия?) требуется еще больше), затем проверьте эти параметры.

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

Возможным реальным динамическим решением было бы реализовать какой-то язык сценариев /xml file/..., который позволяет пользователю указывать виды статуса, их параметры и связывать их с поведением системы.

1

Я не понимаю, почему вы не можете просто написать:

if (customer.Status.IsActive)
  • 0
    Ну, этот пример мог быть упрощенным. Как насчет "if (customer.Status == AccountStatus.ExpiredTrial)" У меня нет свойства для этого в AccountStatus, и я не хочу добавлять свойство для каждого случая, который может возникнуть. Код выше был просто примером ... в моей таблице 14 записей, представляющих различные состояния, в которых может находиться учетная запись клиента.
  • 0
    Вы действительно должны проверить против ExpiredTrial ? Если ExpiredTrial специально определен как !IsActive && CanReactivate - что, как кажется, в вашей таблице - тогда разве вы не должны просто проверить это?
Показать ещё 1 комментарий
0

Я думаю, что дзюдо попытался объяснить - новый статус в БД потребует поставить проверку s для этого нового статуса в условных блоках. Думаю, я тоже что-то делаю. Единственное, что я делаю, это то, что я также использую другое поле "rank", чтобы я мог выполнять сравнение диапазона вместо жесткого кодирования всех статусов. Например, вместо выполнения:

if (customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial)

Если бы я мог привести их в порядок, я мог бы сделать:

if (customer.Status < AccountStatus.Trial), как в нашем перечислении, мы можем поместить их как упорядоченные. Таким образом, новый статус на новой странице не будет ломать логики других страниц (зависит от ранга статуса).

0

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

 public enum AccountStatusCode
 {
      Active = 1,
      Trial = 2,
      Canceled = 3
 }

 public class AccountStatus
 {
      private AccountStatusEnum status
      //
      // Constructor sets initial status.
      public AccountStatus(int status)
      {
          this.Status = (AccountStatusCode)status;
      }

      public int Id { get; set; }
      public string Description { get; set; }
      //
      // 
      public bool IsActive 
      { 
           get { return status == AccountStatusCode.Active; }
      }
      public bool CanReactivate { get; set; }
 }

Обратите внимание, что, поскольку вы сказали, что хотите указать начальный статус учетной записи как int, я принимаю int в конструкторе, но затем я передал его в AccountStatusEnum для назначения его переменной-члену. Вероятно, это не лучшая практика... Вы должны передать конструктору значение AccountStatusCode.

0

Хорошо, если вы работаете в С# 3.0, вы можете попробовать методы расширения:

// Define extension method like this:
public static bool IsActive(this AccountStatus status)      
{            
    get { return status == AccountStatusCode.Active; }      
}
// 
// Call it like this:
if (AccountStatus.IsActive())

Это избавит вас от вашего класса.

  • 0
    я думаю, что это упускает из виду, хотя это заставляет меня думать об альтернативных направлениях. (упуская момент, я имею в виду, что ваше предложение имеет тот же эффект, что и создание перечисления или статического поля, но требует больше кода для этого. Мне нужно было бы добавить расширение для каждой записи в таблице, а не просто перечисление стоимость).
0

Вы можете создать взаимосвязь "многие ко многим" между AccountStatus и описанием. Таким образом, во время выполнения вы можете загружать все различные Описания, которые вы получили, а затем сравнивать с ними, используя какое-то перечисление:)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню