Поведение для имитации перечисления, реализующего интерфейс

Скажем, у меня есть перечисление что-то вроде:

enum OrderStatus
{
    AwaitingAuthorization,
    InProduction,
    AwaitingDespatch
}

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

public static string ToDisplayString(this OrderStatus status)
{
    switch (status)
    {
        case Status.AwaitingAuthorization:
            return "Awaiting Authorization";

        case Status.InProduction:
            return "Item in Production";

        ... etc
    }
}

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

public static SelectList ToSelectList<TEnum>(this TEnum enumObj)

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

: where TEnum has extension ToDisplayString

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

Есть ли у кого-нибудь идеи о том, как я могу реализовать что-то вроде этого?

Ответ 1

Есть ли веская причина использовать enum здесь?

Когда вы начинаете прыгать через сумасшедшие обручи, чтобы использовать enum s, возможно, пришло время использовать класс.

public class OrderStatus
{
    OrderStatus(string display) { this.display = display; }

    string display;

    public override string ToString(){ return display; }

    public static readonly OrderStatus AwaitingAuthorization
        = new OrderStatus("Awaiting Authorization");
    public static readonly OrderStatus InProduction
        = new OrderStatus("Item in Production");
    public static readonly OrderStatus AwaitingDispatch
        = new OrderStatus("Awaiting Dispatch");
}

Вы потребляете его так же, как enum:

public void AuthorizeAndSendToProduction(Order order, ProductionQueue queue)
{
    if(order.Status != OrderStatus.AwaitingAuthorization) 
    {
        Console.WriteLine("This order is not awaiting authorization!");
        return;
    }
    order.Status = OrderStatus.InProduction;
    queue.Enqueue(order);
}

Строковое представление встроено, и все, что вам нужно, это ToString().

Ответ 2

Вместо использования "ToDisplayString" просто переопределите ToString() вашего перечисления. Поэтому, если перечисление переопределяет это, оно примет это, в противном случае оно примет поведение ToString по умолчанию (в ToSelectList).

Ответ 3

Конечно, вы можете использовать DisplayAttribute для аннотирования ваших Enums.

enum OrderStatus
{
  [Display(Description="Long Desc", Name="Awaiting Authorization", ShortName="Wait Auth")]
  AwaitingAuthorization,

  [Display(Description="...", Name="...", ShortName="...")]
  InProduction,

  [Display(Description="...", Name="...", ShortName="...")]       
  AwaitingDespatch
}

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

Метод расширения:

public static class EnumExtensions
{
  public static string ToName(this Enum enumValue)
  {
    var displayAttribute = enumValue.GetType()
      .GetMember(enumValue.ToString())[0]
      .GetCustomAttributes(false)
      .Select(a => a as DisplayAttribute)
      .FirstOrDefault();

    return displayAttribute?.Name ?? enumValue.ToString();

  }
}

С

public enum Test
{
    [Display(Name="AAA")]
    a,
    b
}

Код:

Console.WriteLine(Test.a.ToName());
Console.WriteLine(Test.b.ToName());

Результаты

AAA

б

Я хочу связать свои перечисления с SelectList с помощью метода расширения:

Для безопасности типов я бы не использовал методы расширения, а вместо этого статический класс, который работает с типом Enum:

Версия до С# 7.3. Поскольку у нас нет Enum до 7.3, это вызывает исключения во время выполнения, если мы пытаемся использовать значение, которое не является Enum.

public static class Enums<TEnum>
     where TEnum : struct, IComparable, IFormattable, IConvertible
{
    static Enums()
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new InvalidOperationException();
        }
    }
}

Версия С# 7. 3+, с проверкой времени компиляции... ууу!

public static class Enums<TEnum>
   where TEnum : Enum
{
}

Метод GetValues для класса:

  public static IEnumerable<TEnum> GetValues(bool includeFirst)
  {
     var result = ((TEnum[])Enum.GetValues(typeof(TEnum)))
        .ToList();

     if (!includeZero)
     {
        result = result.Where(r => r != default).ToList();
     }

     return result;
  }

Если вы следуете Рекомендациям по перечислению и включите значение по умолчанию (ноль), мы можем его игнорировать (иногда мы хотим отобразить значение, например "Нет выбранных", а иногда нет "Неправильный выбор").

Затем мы можем добавить еще один метод:

  public static IEnumerable<string> GetNames(bool includeFirst)
  {
     var result = GetValue(includeFirst)
       .Select(v => v.ToName())
       .ToList();

     return result;
  }

Ответ 4

Вы можете сделать это:

public static string ToOrderStatusDisplayString(this Enum status)
{    
    switch ((OrderStatus)status)
    {
         ...
    }
}

Затем ограничьте TEnum Enum: where TEnum : System.Enum

Конечно, таким образом вы получаете кучу методов в самом Enum и теряете безопасность типов.