【问题标题】:Behaviour to simulate an enum implementing an interface模拟实现接口的枚举的行为
【发布时间】:2010-06-09 15:59:54
【问题描述】:

假设我有一个类似的枚举:

enum OrderStatus
{
    AwaitingAuthorization,
    InProduction,
    AwaitingDespatch
}

我还在我的枚举上创建了一个扩展方法来整理 UI 中显示的值,所以我有类似的东西:

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

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

        ... etc
    }
}

受到优秀帖子here 的启发,我想使用扩展方法将我的枚举绑定到SelectList

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

但是,要在 UI 下拉列表中使用 DisplayString 值,我需要沿以下行添加约束

: where TEnum has extension ToDisplayString

显然,这些都不适用于当前的方法,除非有一些我不知道的聪明技巧。

有人对我如何实现这样的事情有任何想法吗?

【问题讨论】:

    标签: c# interface enums extension-methods


    【解决方案1】:

    在这里使用enum 是否有令人信服的理由?

    当您开始使用enums 时,可能是时候使用类了。

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

    【讨论】:

    • 我经常使用这个实现。
    • 太棒了。小警告:您不能使用默认值,或者您会得到“'' 的默认参数值必须是编译时常量。
    • @AYS 没错。默认值必须是null,然后在函数体顶部附近分配所需的默认值(如果是null)。
    • 没试过,但是你可以通过使用结构来绕过这个限制吗?
    • @Kjara 使用属性只有在设计方面才有意义——在 C# 中,最好不要直接公开字段。使用struct 而不是class 意味着null 不能分配给OrderStatus,这是有道理的,因为答案是试图模拟一个枚举。就性能而言:1)属性会导致额外的调用,但这可以忽略不计,我们甚至可以确信 JITter 会摆脱它; 2) 使用struct 不会影响任何事情,因为我们拥有的唯一实例是字段(属性支持字段),因此它们必须存在于堆中。
    【解决方案2】:

    当然,您可以使用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
    }
    

    您还可以选择创建一个扩展方法,获取任何枚举值并根据为其设置的属性返回其显示名称,以整理 UI 中显示的值,如下所示:

    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

    b

    我想使用扩展方法将我的枚举绑定到 SelectList:

    为了类型安全,我不会使用扩展方法,而是使用处理 Enum 类型的静态类:

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

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

    如果您关注Enum Guidelines 并包含默认(零)值,我们可以忽略它(有时我们希望显示“未选择”之类的值,有时我们不显示“无效选择”)。

    然后我们可以添加另一个方法:

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

    【讨论】:

      【解决方案3】:

      不要使用“ToDisplayString”,只需覆盖枚举的 ToString()。因此,如果枚举覆盖它,它将采用它,否则它将采用默认的 ToString 行为(在 ToSelectList 中)。

      【讨论】:

        【解决方案4】:

        如果您只需要使用相对较小的枚举类,它们只有一个显式强制转换运算符 ToString,并且对于 System 及其派生命名空间上的枚举的特殊类不具有其他可用性,那么下面的示例可以成为一个解决方案:

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

        【讨论】:

          【解决方案5】:

          你可以这样做:

          公共静态字符串 ToOrderStatusDisplayString(此枚举状态) { 开关((订单状态)状态) { ... } }

          然后将 TEnum 限制为 Enum:where TEnum : System.Enum

          当然,这样你会在 Enum 本身上获得一堆方法并失去类型安全性。

          猜你喜欢
          • 1970-01-01
          • 2020-11-28
          • 2011-02-12
          • 1970-01-01
          • 1970-01-01
          • 2013-05-11
          • 2013-12-07
          • 2016-09-03
          • 1970-01-01
          相关资源
          最近更新 更多