模拟实现接口的枚举的行为

发布于 2024-09-05 07:57:47 字数 949 浏览 6 评论 0原文

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

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

受到优秀帖子的启发 此处,我想将我的枚举绑定到 SelectList 具有扩展方法:

public static SelectList ToSelectList(this TEnum enumObj)

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

: where TEnum has extension ToDisplayString

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

A君 2024-09-12 07:57:47

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

当您开始疯狂地使用枚举时,可能是时候使用类了。

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

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

記柔刀 2024-09-12 07:57:47

当然,您可以使用 DisplayAttribute 来注释您的 Enum

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

如果您遵循枚举指南 并包含默认(零)值,我们可以忽略它(有时我们想显示“未选择”之类的值,有时我们不想显示“无效选择”)。

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

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

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;
}
一紙繁鸢 2024-09-12 07:57:47

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

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

迟到的我 2024-09-12 07:57:47

如果您只需要使用相对较小的枚举类,这些类仅具有显式转换运算符 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";
                ...
            }
        }
    }
}

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";
                ...
            }
        }
    }
}
再见回来 2024-09-12 07:57:47

您可以这样做:

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文