C #: Enum anti-patterns

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

Например, я видел такой код:

switch(enumValue) { case myEnum.Value1: // ... break; case myEnum.Value2: // ... break; } 

Это на один шаг лучше, чем switch-statements с магическими строками, но это, вероятно, могло бы быть лучше решено с завода, контейнера или другого шаблона.

Или даже код старой школы:

 if(enumValue == myEnum.Value1) { // ... } else if (enumValue == myEnum.Value2) { // ... } 

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

Я думаю, что Enums весьма полезны. Я написал несколько расширений для Enum, которые добавили еще большую ценность для его использования

Во-первых, существует метод расширения описания

 public static class EnumExtensions { public static string Description(this Enum value) { var entries = value.ToString().Split(ENUM_SEPERATOR_CHARACTER); var description = new string[entries.Length]; for (var i = 0; i < entries.Length; i++) { var fieldInfo = value.GetType().GetField(entries[i].Trim()); var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); description[i] = (attributes.Length > 0) ? attributes[0].Description : entries[i].Trim(); } return String.Join(", ", description); } private const char ENUM_SEPERATOR_CHARACTER = ','; } 

Это позволит мне определить en enum следующим образом:

  public enum MeasurementUnitType { [Description("px")] Pixels = 0, [Description("em")] Em = 1, [Description("%")] Percent = 2, [Description("pt")] Points = 3 } 

И получить метку, выполнив это: var myLabel = rectangle.widthunit.Description() (исключая необходимость в var myLabel = rectangle.widthunit.Description() switch ).

Это будет возвращать «px», если rectangle.widthunit = MeasurementUnitType.Pixels или он вернет «px, em», если rectangle.widthunit = MeasurementUnitType.Pixels | MeasurementUnitType.Em rectangle.widthunit = MeasurementUnitType.Pixels | MeasurementUnitType.Em .

Тогда есть

  public static IEnumerable GetIntBasedEnumMembers(Type @enum) { foreach (FieldInfo fi in @enum.GetFields(BindingFlags.Public | BindingFlags.Static)) yield return (int)fi.GetRawConstantValue(); } 

Который позволит мне пройти через любое перечисление с значениями на основе int и вернуть сами значения int.

Я считаю, что они очень полезны в полезной концепции.

Я вижу наличие двух операторов switch как симптом не-OO-дизайна, как объясняется далее в этом ответе .

Это не ответ, так же как внесение вклада в список Enum anti-patterns.

Во время обзора кода этим утром я столкнулся с случаем, похожим на следующее, все в одном classе.

Два случая:

  1. Перед употреблением
  2. После выпивки

..

  public enum ListEnum { CategoryOne, CategoryTwo, CategoryThree, CategoryFour } public class UIELementType { public const string FactoryDomain = "FactoryDomain"; public const string Attributes = "Attributes"; } 

Использование перечислений в не анти-шаблоне. В некоторых книгах о рефакторинге этот код используется, чтобы продемонстрировать, как заменить его polymorphismом. Было бы хорошо, если вы злоупотребляете enumsми в коде.

Все зависит от того, что вы пытаетесь сделать с перечислением.

  1. Если вы пытаетесь запретить своим разработчикам передавать магические числа в свои операции, и вы хотите сохранить целостность ссылочной базы данных с вашей БД, тогда, ДА! Используйте T4-Templates (используя ORM), чтобы перейти в таблицу MeasurementUnitTypes и создать перечисление с столбцами ID, Name и Description, соответствующими enum ‘int, Enum_Name и Description Attribute (хороший подход для дополнительного поля \ data to enum @ danijels) как было предложено выше. Если вы добавите новый тип измерения в свою таблицу MeasurementUnitTypes, вы можете просто щелкнуть правой кнопкой мыши и запустить T4-Template, а код enums будет создан для этой новой строки, добавленной в таблицу. Мне не нравятся жестко закодированные данные в моем приложении, которые не ссылаются на мою БД, поэтому упоминание о подходе T4-Template. Он не расширяется в противном случае … что, если какая-либо другая внешняя система хочет получить наши критерии измерения, используемые в нашей системе, тогда она жестко закодирована в системе, и вы не можете предоставить ее клиенту через службу. Это осталось.

  2. Если цель не связана с данными, и у вас есть некоторая логика, назначенная определенному перечислению, тогда НЕТ! это нарушает принцип SOLID (принцип Open close), так как вы где-то в своем приложении применяете коммутатор или связку Ifs для выполнения логики для enums, ТАКЖЕ, если вы сделали это ДЕЙСТВИТЕЛЬНО плохо, эти переключатели или Ifs на всем протяжении шоу …. удачи добавление нового enum … поэтому он не открыт для расширения и закрыт для модификации, поскольку вам необходимо изменить существующий код, в соответствии с принципом SOLID.

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

     public interface IMeasurementUnitType { int ID { get; } string Description { get; } // Just added to simulate a action needed in the system string GetPrintMessage(int size); } 

Вышеприведенный код определяет интерфейс (кодовый контракт), который должен соблюдать каждый из измерений. Теперь давайте определим Percentage и Pixel:

  public class PixelsMeasurementUnitType : IMeasurementUnitType { public int ID => 1; public string Description => "Pixel"; public string GetPrintMessage(int size) { return $"This is a {Description} Measurement that is equal to {size} pixels of the total screen size"; } } public class PercentMeasurementUnitType : IMeasurementUnitType { public int ID => 2; public string Description => "Persentage"; public string GetPrintMessage(int size) { return $"This is a {Description} Measurement that is equal to {size} persent of total screen size (100)"; } } 

Итак, мы определили два типа: мы будем использовать их в коде следующим образом:

  var listOfMeasurmentTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(s => s.GetTypes()) .Where(p => typeof(IMeasurementUnitType).IsAssignableFrom(p) && !p.IsInterface) .ToList(); 

Здесь мы захватываем все ТИПЫ, которые расширяют интерфейс IMeasurementUnitType, а НЕ сам интерфейс. Теперь мы можем использовать Activator для создания экземпляров classов для заполнения наших элементов интерфейса:

  public IEnumerable GetInstantiatedClassesFromTypes(List types) { foreach (var type in types) { yield return (IMeasurementUnitType)Activator.CreateInstance(type); } } 

Вы можете изменить приведенный выше код, чтобы быть универсальным для любого типа, И СЕЙЧАС имеет место, и клиент дает новый тип единицы измерения, называемый Point как новое требование, мне не нужно ИЗМЕНИТЬ ЛЮБОЙ код, просто добавьте новый тип (расширяйте код НЕ изменяется). Новый тип будет автоматически выбран в приложении.

  public class PointMeasurementUnitType : IMeasurementUnitType { public int ID => 3; public string Description => "Point"; public string GetPrintMessage(int size) { return $"This is a {Description} Measurement that is equal to {size} points of total screen size"; } } 

Хорошей идеей было бы кэшировать ваши типы для повышения эффективности при запуске приложения или попробовать и использовать контейнер DI по вашему выбору.

Кроме того, можно утверждать, что где-то в вашем приложении вам нужно будет различать типы, и я согласен, однако вы хотите сохранить яблоки с яблоками. Поэтому постарайтесь максимально использовать тот же принцип, который используется для этих типов. Если этот тип используется в каком-то classе графического процессора (например), то у вас есть IGraphicsProcessor и есть ваши конкретные classы, которые различают эти типы, например PersentageAndPixelGraphicsProcessor (который простирается от IGraphicsProcessor), или если он отличает только один вызов типа PersentageGraphicsProcessor.

Извините за HUGE SA, но мне очень нравится enum, но я чувствую, когда вы пытаетесь отделить логику, используя enums, это СИЛЬНЫЙ анти-шаблон.

комментарии приветствуются,