"Enum as immutable rich-object": это анти-шаблон?

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

public enum Movement {
    [DisplayName("Turned Right")]
    TurnedRight,
    [DisplayName("Turned Left")]
    [Description("Execute 90 degree turn to the left")]
    TurnedLeft,
    // ...
}

И у вас был набор методов расширения для поддержки атрибутов:

public static string GetDisplayName(this Movement movement) { ... }
public static Movement GetNextTurn(this Movement movement, ...)  { ... }

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

public class Movement
{
    public int Value { get; set; } // i.e. the type backing the enum
    public string DisplayName { get; set; }
    public string Description { get; set; }
    public Movement GetNextTurn(...) { ... }
    // ...
}

Таким образом, он может "путешествовать" как простое поле во время сериализации, быстро сравниваться и т.д., но поведение может быть "интернализировано" (ala OOP).

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

Ответы

Ответ 1

Я считаю, что это плохой шаблон на С# просто потому, что поддержка языка для объявления и доступа к атрибутам настолько искалечена; они не предназначены для хранения очень больших данных. Это боль, чтобы объявить атрибуты с нетривиальными значениями, и это боль, чтобы получить значение атрибута. Как только вы захотите что-то отдаленно интересное, связанное с вашим перечислением (например, метод, который вычисляет что-то в перечислении или атрибут, который содержит не примитивный тип данных), вам нужно либо реорганизовать его в класс, либо поместить другое в какое-то внеполосное место.

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

Ответ 2

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

public enum Movement
{
    TurnedRight,
    TurnedLeft,
    Stopped,
    Started
}

Теперь, скажем, потребность расширяется, чтобы быть чем-то более точным; скажем, изменение заголовка и/или скорости, превратив одно "поле" в ваш "псевдокласс" на два:

public sealed class Movement
{
    double HeadingDelta { get; private set; }
    double VelocityDelta { get; private set; }
    // other methods here
}

Итак, у вас есть кодифицированное перечисление, которое теперь должно быть преобразовано в неизменяемый класс, потому что теперь вы отслеживаете два ортогональных (но все еще неизменяемых) свойства, которые действительно принадлежат друг другу в одном интерфейсе. Любой код, который вы написали против вашего "богатого enum", теперь должен быть потрошен и переработан значительно; тогда как если бы вы начали с него как с классом, у вас, вероятно, было бы меньше работы.

Вы должны спросить, как код будет поддерживаться с течением времени, и если богатое перечисление будет более удобным для обслуживания, чем класс. Моя ставка заключается в том, что он не будет более ремонтопригодным. Кроме того, как указывал mquander, подход на основе классов более идиоматичен в С#.

Что-то еще нужно учитывать. Если объект является неизменным и является struct, а не class, вы получаете такую ​​же семантику pass-by-value и незначительные различия в размере сериализации и размере времени выполнения объекта, как и с перечислением.