如何创建一个带有数据的“枚举”?

4
我有一个投票类,其中一个属性是投票类型,例如一致同意、3/4选举、简单多数等。每种类型都需要与之关联的字符串来描述投票类型(如“简单多数需要51%通过”等)。我需要将这些投票类型/描述与我的视图模型一起传递到视图中,然后可以使用它来制作下拉列表。
然后,当提交创建投票的表单时,我只需要将投票类型(不带描述)绑定到投票模型(它是视图模型的一部分)。
我只使用C#很短的时间,不太了解它中的枚举如何工作。也许枚举不是这个问题的解决方法。
public class VoteViewModel
{
    public VoteViewModel()
    {
        Vote = new Vote();
    }

    public Vote Vote { get; set; }
    public int EligibleVoters { get; set; }
}

这是我将放置下拉菜单的位置。

<section class="vote-type">
    <select name="">
        <option value="">Select Vote Type</option>
    </select>
    <section class="vote-type-info">
        <p class="vote-rules">To pass this vote, at least 51% of Eligible Voters must vote to approve it.</p>
    </section>
</section>

您可以使用自定义属性。如何获取枚举的自定义属性值?https://dev59.com/MW435IYBdhLWcg3w-1Z_ - ISZ
简单多数需要超过50%的票数才能通过:这是错误的说法。正确的表述应该是“简单多数需要超过50%的票数才能通过。” - phoog
6个回答

4
请注意,我只展示字符串内容,但实际上可能是任何类型。在每种情况下,我都会说明如何扩展以适用更多值(如果有可能)。

将枚举用作键

您可以将枚举类型用作字典的键(为了唯一性,请将其设置为某个辅助类中的静态和只读属性):

private static readonly Dictionary<MyEnum, string> _dict =
{
    //Using dictionary initialization
    {MyEnum.MyValue, "The text for MyValue"},
    {MyEnum.MyOtherValue, "Some other text"},
    {MyEnum.YetAnotherValue, "Something else"}
}

public static readonly Dictionary<MyEnum, string> Dict
{
    get
    {
        return _dict;
    }
}

获取相关联的值:

string text = Dict[MyEnum.MyValue];

或者使用:
string text;
if (Dict.TryGetValue(MyEnum.MyValue, out text))
{
    //It has the value
}
else
{
    //It doesn't have the value
}

通过这种方式,您可以访问与枚举值相关联的字符串。然后,您可以公开您的Dictionary,以便您可以读取相应的值。

如果需要存储多个值,则需要使用复杂类型。只需使用自定义类型而不是字符串即可。或者如果可用,可以使用元组。

访问字典可能意味着额外的麻烦,但希望它也不会意味着线程问题。


Enum.GetName

您可以使用Enum.GetName读取枚举的值的名称:

string text = Enum.GetName(MyEnum.MyValue);
//text will have the text "MyValue"

//or
var some = MyEnum.MyValue;
string text = Enum.GetName(some);

注意:ToString() 也应该可以工作。
不幸的是,这对于除字符串以外的其他内容将无法起作用。
此外,它的缺点是您无法在那里放置任何文本(它必须是有效的标识符)。

自定义属性

您将需要声明一个属性类型:
[AttributeUsage(AttributeTargets.Field)]
public class EnumValueAttribute : System.Attribute 
{
    public readonly string _value;
    public string Value
    {
        get
        {
            return _value;
        }
    }
    public EnumValueAttribute(string value)  // value is a positional parameter
    {
        //beware: value can be null...
        // ...but we don't want to throw exceptions here
        _value = value;
    }
}

现在您需要将此属性应用于您的枚举类型:
public enum MyEnum
{
    [EnumValue("The text for MyValue")]
    MyValue = 1,
    [EnumValue("Some other text")]
    MyOtherValue = 2,
    [EnumValue("Something else")]
    YetAnotherValue = 3
}

最后,您需要读取属性的值:
public static string GetValue(MyEnum enumValue)
{
    FieldInfo fieldInfo = typeof(MyEnum).GetField(enumValue.ToString());
    if (!ReferenceEquals(fieldInfo, null))
    {
        object[] attributes = fieldInfo.GetCustomAttributes(typeof(EnumValueAttribute), true);
        if (!ReferenceEquals(attributes, null) && attributes.Length > 0)
        {
            return ((EnumValueAttribute)attributes[0]).Value;
        }
    }
    //Not valid value or it didn't have the attribute
    return null;
}

现在你可以称之为:
string text1 = GetValue(MyEnum.MyValue);
//text1 will have the text "MyValue"
//or
var some = MyEnum.MyValue;
string text2 = GetValue(some);

您可以向属性类添加更多字段,并使用它们来传递所需的任何其他值。

但这需要反射,并且如果您在沙盒中运行,则可能无法使用。此外,它将每次检索属性,从而在过程中创建一些短暂的对象。


模拟枚举

您可以使用没有公共构造函数并公开其自身的静态只读实例的密封类来模拟枚举:

public sealed class MyEnumEmu
{
    private static readonly string myValue = new MyEnumEmu("The text for MyValue");
    private static readonly string myOtherValue = new MyEnumEmu("Some other text");
    private static readonly string yetAnotherValue = new MyEnumEmu("Something else");

    public static MyEnumEmu MyValue
    {
        get
        {
            return myValue;
        }
    }

    public static MyEnumEmu MyOtherValue 
    {
        get
        {
            return myOtherValue;
        }
    }

    public static MyEnumEmu YetAnotherValue
    {
        get
        {
            return yetAnotherValue;
        }
    }

    private string _value;

    private MyEnumEmu(string value)
    {
        //Really, we are in control of the callers of this constructor...
        //... but, just for good measure:
        if (value == null)
        {
            throw new ArgumentNullException("value");
        }
        else
        {
            _value = value;
        }
    }

    public string Value
    {
        get
        {
            return _value;
        }
    }
}

像往常一样使用它:

var some = MyEnumEmu.MyValue;

并访问相关值:

string text = MyEnumEmu.MyValue.Value;
//text will have the text "MyValue"
//or
string text = some.Value;

这是最灵活的选择,您可以使用复杂类型而非字符串或添加额外字段来传递多个值。

但是...它并不是真正的枚举。


+1 对于详细的回答。关于封闭类方法的说明,您会失去枚举强类型特性的一些好处。在您的情况下,我可以这样做 if (text == "asd") 或者 string text = MyEnumEmu.MyValue.Value; text = SomeOtherEnum.MyValue.Value 等等... 但是对于枚举来说是不可能的(除非进行一些显式转换等)。此外,使用字典的话,很有可能会完全搞乱数据。在这里,采用描述方法是最好的选择。 - nawfal
你可以使用自定义属性和反射来创建(static)字典,并将字典存储在一些全局可用的位置。 - Zev Spitz
你不需要在第一个代码块的第一行末尾添加new()表达式吗? - REMCodes

3
您可以围绕您的枚举创建一个“常量”字典(更确切地说是只读静态,因为您无法创建常量字典)。
public enum VoteType { Unanimous = 1, SimpleMajority = 2, ... }

public static readonly Dictionary<VoteType, string> VoteDescriptions = new Dictionary<VoteType, string>
{
    { VoteType.Unanimous, "Unanimous description" },
    { VoteType.SimpleMajority, "Simple majority" },
    ...
};

1
这还有一个额外的优点,就是将来可以本地化。 - Matt
3
唯一的不足之处是,如果其中任何一个更改,描述和枚举项很容易失去同步... - Blueberry
@Blueberry 真的没错;通过为枚举项分配确定的值可能会减轻这个问题(我在答案中添加了这一点)。 - McGarnagle
1
一个稍微保守的方法是使用以下代码:public static readonly Dictionary<VoteType, string> VoteDescriptions = Enum.GetValues(typeof(T)).Cast<T>().ToDictionary(x => x, x => ""); 并在静态构造函数中更新值部分。这样至少你就不会忘记添加枚举值了。 - nawfal

1
public class Vote()
{
     public VoteType VoteSelectType { get; set; }
}

public enum VoteType
{
    [Display(Name = "Enter Text Here")]
    unanimous = 1,
    [Display(Name = "Enter Text Here")]
    threequatervote = 2,
    [Display(Name = "Enter Text Here")]
    simplymajority = 3
}

请前往此处,这基本上是您的解决方案如何使用枚举值填充下拉列表?


0
我以前用过这个,真的很方便。

http://www.codeproject.com/Articles/13821/Adding-Descriptions-to-your-Enumerations

简而言之,它让你能够做的是:
public enum MyColors{
   [Description("The Color of my skin")]
   White,
   [Description("Bulls like this color")]
   Red,
   [Description("The color of slime")]
   Green
}

然后通过简单调用来获取描述:

String desc = GetDescription(MyColor.Green);

它确实使用了反射,因此在简单性和轻微性能损失之间需要权衡。大部分情况下,我会选择性能损失...

0

既然你有typedescription,我建议你创建一个包含两者的class,而不是使用enum。优点是可以减少更多的工作量,并且非常灵活。

public class VoteType
{
   public string Name{ get; set; }
   public string Description{ get; set; }
}

现在你的Vote类将引用这个VoteType。
public class Vote
{
   ...
   public VoteType Type{ get; set; }
}

在你的VoteViewModel中最好有一个包含所有VoteType的类。
public class VoteViewModel
{
   ...
   public IEnumerable<SelectListItem> VoteTypes{ get; set; }
}

现在你可以轻松地将VoteTypes绑定到下拉列表中。
@model VoteViewModel

@Html.DropDiwnListFor(m => m.VoteTypes,...)

0

如果您想要,可以使用枚举,但是您需要决定如何将枚举值与您要显示的内容进行关联。例如,一个名为 SimpleMajority 的枚举值可能会显示为 "Simple Majority"。一种实现方法是使用 Description 属性和一个辅助类,如 here 中所述。

然而,您可能会发现设置一个轻量级的集合类来存储投票类型的值和描述更容易。这可以简单地使用 Dictionary<int, string> 实现。您可能会觉得这种方法更加直接。


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