19

Say I have an enum something like:

enum OrderStatus
{
    AwaitingAuthorization,
    InProduction,
    AwaitingDespatch
}

I've also created an extension method on my enum to tidy up the displayed values in the UI, so I have something like:

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

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

        ... etc
    }
}

Inspired by the excellent post here, I want to bind my enums to a SelectList with an extension method:

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

however, to use the DisplayString values in the UI drop down I'd need to add a constraint along the lines of

: where TEnum has extension ToDisplayString

Obviously none of this is going to work at all with the current approach, unless there's some clever trick I don't know about.

Does anyone have any ideas about how I might be able to implement something like this?

5 Answers 5

34

Is there a compelling reason to use an enum here?

When you start jumping through crazy hoops to use enums, it might be time to use a class.

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");
}

You consume it the same as an 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);
}

The string representation is built-in, and all you need is ToString().

Sign up to request clarification or add additional context in comments.

8 Comments

I use this implementation a lot.
Brilliant. Small caveat: you can't use default values, or you get "Default parameter value for '<type>' must be a compile-time constant.
@AYS That is correct. The default would have to be null and then the desired default assigned (if null) near the top of the function body.
haven't tried it, but can you get around that restriction by using a struct?
@Kjara Using properties does only make sense design–wise — in C# it is better to never expose fields directly. Using a struct rather than a class means null cannot be assigned to OrderStatus, which makes sense since the answer is trying to emulate an enum. As far as performance is concerned: 1) properties cause an additional call, but that's negligible and we can even be confident that the JITter will get rid of it; 2) using a struct won't impact anything, since the only instances we have are fields (properties backing fields) and so they have to live in the heap.
|
2

Of course, you can use the DisplayAttribute to annotate your Enums.

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

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

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

You can also opt to create an extension method taking any enumeration value and returning its display name based on the attribute set to it to tidy up the displayed values in the UI, as follows:

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();
    }
}

With

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

Code:

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

Results

AAA

b

I want to bind my enums to a SelectList with an extension method:

For type safety, I wouldn't use an extension methods, but instead a static class that deals with the Enum type:

Pre C# 7.3 version. Since Enum is not a valid type constraint prior to 7.3 (and it would cause a compile-time exception), you'll end up by considering that enums are value types and they implement some interfaces, in order to restrict the type parameter as close to Enum as possible.

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

C# 7.3+ version, with compile time checking... yay!

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

GetValues Method for the class:

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;
}

If you follow Enum Guidelines and include the Default (zero) value, we can ignore it (sometimes we want to display the value like "None Selected" and sometimes we don't "Invalid Selection").

Then we can add another method:

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

Comments

1

Instead of using "ToDisplayString", simply override ToString() of your enum. So if an enum overrides it it will take it, otherwise it will take the default ToString behavior (in ToSelectList).

Comments

0

If you just need to use relatively tiny enumerate classes that have no more than an explicit casting operator, ToString and do not take other usability for the special ones about enum on System and its derived namespaces, then the following example could be a solution:

namespace MyNamespace {
    public abstract class EnumerateClass<Type, InheritingClass> : IEquatable<InheritingClass>
        where Type : IEquatable<Type>
        where InheritingClass : EnumerateClass<Type, InheritingClass> {

        internal readonly Type Identifier;

        protected EnumerateClass (Type identifier) {
            this.Identifier = identifier;
        }
        public bool Equals(InheritingClass obj)
            => this.Identifier.Equals(obj.Identifier);
        public static explicit operator Type(EnumerateClass<Type, InheritingClass> obj)
            => obj.Identifier;
    }
    public sealed class MyNumber : EnumerateClass<int, MyNumber> {

        private MyNumber(int identifier) : base(identifier) { }

        public static readonly MyNumber None = new Number(0);
        public static readonly MyNumber One = new Number(1);
        public static readonly MyNumber Two = new Number(2);
        ...

        public override string ToString() {
            switch (this.Identifier) {
                case 0: return "None";
                case 1: return "One";
                case 2: return "Two";
                ...
            }
        }
    }
}

Comments

-4

You could do this:

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

Then restrict TEnum to Enum: where TEnum : System.Enum

Of course, that way you get a bunch of methods on the Enum itself and lose type safety.

1 Comment

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.