在C#中扩展枚举类型

29

在Java中,我习惯于扩展枚举值或像这样覆盖方法:

    enum SomeEnum
    {

        option1("sv")
        {
            public String toString()
            {
                return "Some value";
            }     
        }, 
        option2;

        private String PassedValue;

        public SomeEnum(String somevalue)
        {
            this.PassedValue = somevalue;
        }

        public SomeEnum()
        {
                this.PassedValue = "Default Value";
        }

        public String getPassedValue()
        {
            return this.PassedValue;
        }

    }

在C#中是否有类似的方法,或者说枚举在C#中更受限制?


已经存在一个非常相似的问题,并且有一些很好的答案。你可以在这里找到所有需要的内容:https://dev59.com/NnRB5IYBdhLWcg3w6bYE。 - Hari Menon
5个回答

41

我希望在.NET中,枚举类型更加强大。但是我喜欢.NET!你可以使用属性来实现相同的功能。只需编写下面的代码一次,然后在任何地方使用它。这将是一个很长的答案,但我认为这是一个相当不错的解决方案,所以请耐心等待!

用法

SomeEnum e = SomeEnum.ValueTwo;
string description = e.GetDescription();

枚举

使用属性来描述枚举及其取值。

[DescriptiveEnumEnforcement(DescriptiveEnumEnforcement.EnforcementTypeEnum.ThrowException)]
public enum SomeEnum
{
    [Description("Value One")]
    ValueOne,

    [Description("Value Two")]
    ValueTwo,

    [Description("Value 3")]
    ValueThree
}

DescriptionAttribute

/// <summary>Indicates that an enum value has a description.</summary>
[AttributeUsage(AttributeTargets.Field)]
public class DescriptionAttribute : System.Attribute
{
    /// <summary>The description for the enum value.</summary>
    public string Description { get; set; }

    /// <summary>Constructs a new DescriptionAttribute.</summary>
    public DescriptionAttribute() { }

    /// <summary>Constructs a new DescriptionAttribute.</summary>
    /// <param name="description">The initial value of the Description property.</param>
    public DescriptionAttribute(string description)
    {
        this.Description = description;
    }

    /// <summary>Returns the Description property.</summary>
    /// <returns>The Description property.</returns>
    public override string ToString()
    {
        return this.Description;
    }
}

描述性枚举强制属性

一个属性,用于确保您的枚举已正确配置。

/// <summary>Indicates whether or not an enum must have a NameAttribute and a DescriptionAttribute.</summary>
[AttributeUsage(AttributeTargets.Enum)]
public class DescriptiveEnumEnforcementAttribute : System.Attribute
{
    /// <summary>Defines the different types of enforcement for DescriptiveEnums.</summary>
    public enum EnforcementTypeEnum
    {
        /// <summary>Indicates that the enum must have a NameAttribute and a DescriptionAttribute.</summary>
        ThrowException,

        /// <summary>Indicates that the enum does not have a NameAttribute and a DescriptionAttribute, the value will be used instead.</summary>
        DefaultToValue
    }

    /// <summary>The enforcement type for this DescriptiveEnumEnforcementAttribute.</summary>
    public EnforcementTypeEnum EnforcementType { get; set; }

    /// <summary>Constructs a new DescriptiveEnumEnforcementAttribute.</summary>
    public DescriptiveEnumEnforcementAttribute()
    {
        this.EnforcementType = EnforcementTypeEnum.DefaultToValue;
    }

    /// <summary>Constructs a new DescriptiveEnumEnforcementAttribute.</summary>
    /// <param name="enforcementType">The initial value of the EnforcementType property.</param>
    public DescriptiveEnumEnforcementAttribute(EnforcementTypeEnum enforcementType)
    {
        this.EnforcementType = enforcementType;
    }
}

获取描述

/// <summary>Provides functionality to enhance enumerations.</summary>
public static partial class EnumUtil
{
    /// <summary>Returns the description of the specified enum.</summary>
    /// <param name="value">The value of the enum for which to return the description.</param>
    /// <returns>A description of the enum, or the enum name if no description exists.</returns>
    public static string GetDescription(this Enum value)
    {
        return GetEnumDescription(value);
    }

    /// <summary>Returns the description of the specified enum.</summary>
    /// <param name="value">The value of the enum for which to return the description.</param>
    /// <returns>A description of the enum, or the enum name if no description exists.</returns>
    public static string GetDescription<T>(object value)
    {
        return GetEnumDescription(value);
    }

    /// <summary>Returns the description of the specified enum.</summary>
    /// <param name="value">The value of the enum for which to return the description.</param>
    /// <returns>A description of the enum, or the enum name if no description exists.</returns>
    public static string GetEnumDescription(object value)
    {
        if (value == null)
        return null;

        Type type = value.GetType();

        //Make sure the object is an enum.
        if (!type.IsEnum)
            throw new ApplicationException("Value parameter must be an enum.");

        FieldInfo fieldInfo = type.GetField(value.ToString());
        object[] descriptionAttributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

        //If no DescriptionAttribute exists for this enum value, check the DescriptiveEnumEnforcementAttribute and decide how to proceed.
        if (descriptionAttributes == null || descriptionAttributes.Length == 0)
        {
            object[] enforcementAttributes = fieldInfo.GetCustomAttributes(typeof(DescriptiveEnumEnforcementAttribute), false);

            //If a DescriptiveEnumEnforcementAttribute exists, either throw an exception or return the name of the enum instead.
            if (enforcementAttributes != null && enforcementAttributes.Length == 1)
            {
                DescriptiveEnumEnforcementAttribute enforcementAttribute = (DescriptiveEnumEnforcementAttribute)enforcementAttributes[0];

                if (enforcementAttribute.EnforcementType == DescriptiveEnumEnforcementAttribute.EnforcementTypeEnum.ThrowException)
                    throw new ApplicationException("No Description attributes exist in enforced enum of type '" + type.Name + "', value '" + value.ToString() + "'.");

                return GetEnumName(value);
            }
            else //Just return the name of the enum.
                return GetEnumName(value);
        }
        else if (descriptionAttributes.Length > 1)
            throw new ApplicationException("Too many Description attributes exist in enum of type '" + type.Name + "', value '" + value.ToString() + "'.");

        //Return the value of the DescriptionAttribute.
        return descriptionAttributes[0].ToString();
    }
}

我可以使用属性类为枚举类型提供一组值吗?我是否仍需要使用扩展方法来检索这些值,或者属性会添加此功能?我想要使用SomeEnum.Option1.getPassedValue(); 来检索我正在编写的AtomicParsley应用程序的格式字符串。 - Chris McGrath
1
我喜欢欧芹!好吃。我发布的这个代码将完全按照您所要求的进行操作,请参阅上面的“使用”部分。您可以以任何想要的方式检索值,我将其制作为扩展方法,因为这只会使调用该方法更加简洁。 - Josh M.
我曾经苦恼于如何对所有 T 值进行通用枚举并获取描述,结果发现我的枚举转换有误。如果 e 是你的枚举值作为 int,而 enumType 是该枚举的 Type,你可以使用 (Enum.ToObject(enumType, e) as Enum).GetDescription() 获取描述。 - Superman.Lopez
你要如何将描述信息转换回底层枚举值。 - Reahreic
你不应该使用描述作为你传递的“键”,而应该使用值。描述只应用于显示目的。所以回答你的问题,你永远不应该从枚举值的描述中进行翻译。 - Josh M.

26

C#中的枚举仅适用于(整数)值; 它们不能像Java中那样具有特殊的方法或构造函数。

但是,您可以定义扩展方法来处理枚举以实现几乎相同的效果:

public enum MyEnum {
    Foo = 1,
    Bar = 2,
    Default = Foo
}

public static class MyEnumExtensions
{
    public static Widget ToWidget(this MyEnum enumValue) {
        switch (enumValue) {
        case MyEnum.Foo:
            return new Widget("Foo!");

        case MyEnum.Bar:
            return new Widget("Bar...");

        default:
            return null;
        }
    }
}

那么您可以说:

var val = MyEnum.Foo;
var widget = val.ToWidget();

有一种更干净的方法可以使用属性来完成这个任务,而不需要硬编码字面量。 - KeithS
3
@Keith: 嗯,是的,这个例子有点复杂。我只是想展示如何使用扩展方法与枚举 :-) - Cameron
1
@KeithS 但是你正在将字面值硬编码到属性中。 - cubesnyc
属性值,例如“描述”属性或类似工作的属性,可以是对可变数据的引用;应用程序设置、资源文件、INI、数据库查询。更改该数据,您将更改标签,无需重新编译,除非您正在添加或删除它们(在这种情况下,您可能需要重新编译以使用新值)。此外,属性将硬编码集中在枚举定义中;比在扩展方法库中更容易找到。 - KeithS
我个人更喜欢这种解决方案,因为即使它使用了硬编码的字符串字面量,但它并没有使用反射,所以通常应该运行得更快并且使用更少的分配。 - Yoshi Askharoun

3

C#中的枚举基本上只是命名的基元。它们通常基于int,但可以基于任何数字基元。因此,C#提供的功能远不及Java的枚举。权衡之处在于C#枚举更加轻量级,而Java枚举是完整的对象。

public enum FooBar : int {
    Foo = 1,
    Bar = 2
}

上述枚举与 int 没有太大的区别,只是我们现在可以使用 FooBar.Foo 来代替文字 1。你可以在枚举值和整数之间进行强制类型转换,Enum 还有一些辅助函数可帮助处理枚举。但这基本就是 C# 中的枚举了,它们与 C 或 C++ 的枚举很相似。


2

在C#中,您可以使用扩展方法来实现类似的功能。

enum Test
{
    Value1,
    Value2,
    Value3
}

static class TextExtensions
{
    public static string Value(this Test value)
    {
        string stringValue = default(String);

        switch (value)
        {
            case Test.Value1:
                {
                    stringValue = "some value 1";
                } break;

            case Test.Value2:
                {
                    stringValue = "some value 2";
                }; break;

            case Test.Value3:
                {
                    stringValue = "some value 3";
                }; break;
        }

        return stringValue;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.Write(Test.Value1.Value());
        Console.ReadLine();
    }
}

1
以下是我关于如何包装枚举以实现自定义功能以适应业务需求的实现。它需要一些样板代码,但是可以通过自定义类创建自己的验证和功能,同时仍然获得所有标志枚举的好处。
{{link1:链接1}}提供了更多Flag Enum的优点。
{{link2:.Net Fiddle}}中的代码:
using System;

public class MyClass
{
    /* Flag Enum - Access via FilePerms */
    [Flags] enum Flag_FilePerms : int
    {
        None = 0,
        Create = 1,
        Read = 2,
        Update = 4,
        Delete = 8
    }

    /* FlagEnum base class */
    abstract class FlagEnum
    {
        protected string __alias__;
        protected Type __etype__;
        protected Type __utype__;

        public FlagEnum(string sAlias, Type etype)
        {
            if (!etype.IsEnum)
                throw new Exception($"etype is not an Enum Type. Got: {etype.Name}");
            this.__alias__ = sAlias;
            this.__etype__ = etype;
            this.__utype__ = Enum.GetUnderlyingType(etype);
            // Do validation here. 
            // Eg. ensuring that sequence follows power of 2 with None = 0 as first entry ...
            //     Note: .net does not validate this automatically with the Flags() 
        }
        // implement custom methods...
    }


    /* Enum wrapper */
    class Flag_FilePerms_Wrapper : FlagEnum 
    {
        public Flag_FilePerms None     = Flag_FilePerms.None;
        public Flag_FilePerms Create   = Flag_FilePerms.Create;
        public Flag_FilePerms Read     = Flag_FilePerms.Read;
        public Flag_FilePerms Update   = Flag_FilePerms.Update;
        public Flag_FilePerms Delete   = Flag_FilePerms.Delete;
        public Flag_FilePerms_Wrapper(string sAlias) : base(sAlias, typeof(Flag_FilePerms))
        {
        }
    }
    
    private static void pc(object o){Console.Write($"{o}\n");}
    

    /* singleton for use in business logic */
    static Flag_FilePerms_Wrapper FilePerms = new Flag_FilePerms_Wrapper("FilePerms");


    static void Main(string[] args)
    {
        /* business logic */
        var perms = FilePerms.Update | FilePerms.Create;

        pc($"perms.HasFlag(FilePerms.Create): {perms.HasFlag(FilePerms.Create)}"); /* True */
        pc($"perms.HasFlag(FilePerms.Delete): {perms.HasFlag(FilePerms.Delete)}"); /* False */

        var perms_none = FilePerms.None;

        pc($"perms_none == FilePerms.None: {perms_none == FilePerms.None}"); /* True */
        pc($"perms == FilePerms.None: {perms == FilePerms.None}"); /* False */
    }

}

您可以实现不同的枚举类,并为每个用例实现特定的功能。

例如:

  • FlagEnum:
    • 添加自定义验证以确保无条目和二次幂增量
    • 添加特定于标志处理的功能
  • IntEnum:
    • 限制多个选择
    • 值不可知(不像FlagEnum)
    • 用例:错误代码等
  • StrEnum:
    • 当枚举只有名称而没有指定值时
    • 编写方法以处理字符串作为唯一有效的解析参数
      • 开箱即用的Enum.parse处理字符串和数字值,数字值可能对您的业务案例存在歧义。
    • 用例:工作流状态

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接