Дружественный формат Enum для использования в ComboBoxes, CheckedListBoxes и т.д.

Требования

Я хочу выбрать значения из enum в С# с помощью ComboBox или выбрать битмаски (для enum с атрибутом Flags) с помощью CheckedListBox. Я хочу, чтобы добавить значения в элементы управления в качестве выбираемых элементов и четко указать, какой пользователь выбрал.

Цель 1: Удобство

Я также хочу, чтобы выбор был понятным и приятным для пользователя. В настоящее время я уже могу добавить значения enum в ComboBox или CheckedListBox, но Enum.ToString() вернет имя идентификатора. Pascal Case достаточно хорош для меня, но не для моих пользователей.

Цель 2: Простой код

Я хочу, чтобы это было легко кодировать. Я имею в виду easy. Я хочу, чтобы это было просто запоздалой мыслью для определения и/или использования любого enum значения.

Мое решение

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

Ответ 1

В основе моего решения лежит struct, который может обернуть значения Enum и переопределить ToString().

Введите EnumWrapper:

public struct EnumWrapper
{
    private readonly Enum e;
    public EnumWrapper(Enum e) {
        this.e = e;
    }
    public static implicit operator Enum(EnumWrapper wrapper) {
        return wrapper.e;
    }
    public static explicit operator EnumWrapper(Enum e) {
        return new EnumWrapper(e);
    }
    public override string ToString() {
        return e.ToStringFriendly();
    }
}

Метод ToStringFriendly() определяется как метод расширения на Enum:

using System.Text.RegularExpressions;
public static class _Extensions
{
    public static string ToStringFriendly(this Enum e)
    {
        string s = e.ToString();

        // enforce a charset: letters, numbers, and underscores
        s = Regex.Replace(s, "[^A-Za-z0-9_]", ""); 

        // separate numbers from letters
        s = Regex.Replace(s, "([a-zA-Z])([0-9])", "$1 $2"); 

        // separate letters from numbers
        s = Regex.Replace(s, "([0-9])([a-zA-Z])", "$1 $2"); 

        // space lowercases before uppercase (word boundary)
        s = Regex.Replace(s, "([a-z])([A-Z])", "$1 $2"); 

        // see that the nice pretty capitalized words are spaced left
        s = Regex.Replace(s, "(?!^)([^ _])([A-Z][a-z]+)", "$1 $2"); 

        // replace double underscores with colon-space delimiter
        s = Regex.Replace(s, "__", ": "); 

        // finally replace single underscores with hyphens
        s = Regex.Replace(s, "_", "-"); 

        return s;
    }
}

Теперь, чтобы добавить любое значение Enum в ComboBox, например,

comboBox.Items.Add((EnumWrapper)MyEnum.SomeValue);

И вернуть его (после нулевого тестирования, конечно):

MyEnum myValue = (MyEnum)(Enum)(EnumWrapper)comboBox.SelectedItem;

И что это. Каковы же плюсы и минусы этого подхода?

Плюсы:

  • Вы можете передавать значения перечисления (почти) непосредственно в свои элементы управления и из них. Просто отбрасывайте назад и вперед в/из EnumWrapper.
  • Это хорошо работает для всех значений Enum с паскалем с несколькими особыми случаями. Одним из примеров может быть значение, называемое MyValue__DescriptionOf123_ENUMValue, которое вышло бы из ToStringFriendly() как "My Value: Описание значения 123-ENUM".
  • Механика (a struct и метод расширения) может быть написана один раз и спрятана. В каждом Enum нет дополнительного кодирования. Это означает, что он отлично работает, как указано выше для перечислений, которые вы не пишете, предполагая случай Паскаля, который в .NET является хорошим предположением. Примечание: вот почему я сказал бы этот ответ, какой бы изящной она ни была, не лучшее решение, чем мое, поскольку для этого требуется добавить атрибут TypeConverter каждый Enum.

Против:

  • Это не поддерживает пользовательские описания или несколько языков. Например, значение, подобное Encoding.EBCDIC, всегда будет отображаться как "EBCDIC" и не позволяет вам вручную вводить "расширенный двоично-кодированный код децимального обмена", а тем более другие языки.

Будущая работа

Можно добавить пользовательские описания и многоязычную поддержку, изменив ToStringFriendly(), чтобы выполнить поиск по языку для этого значения перед де-Паскалем.

Подробнее о весе Regex см. этот поток.