将枚举与C#中的字符串相关联

问题描述 投票:267回答:28

我知道以下是不可能的,因为它必须是一个int

enum GroupTypes
{
    TheGroup = "OEM",
    TheOtherGroup = "CMB"
}

从我的数据库中我得到一个包含不全面代码的字段(OEM和CMB)。我想把这个字段变成一个枚举或其他可以理解的东西。因为目标是可读性,所以解决方案应该简洁。 我还有其他选择吗?

c# .net
28个回答
312
投票

我喜欢在类中使用属​​性而不是方法,因为它们看起来更像枚举。

以下是Logger的示例:

public class LogCategory
{
 private LogCategory(string value) { Value = value; }

 public string Value { get; set; }

 public static LogCategory Trace { get { return new LogCategory("Trace"); } }
 public static LogCategory Debug { get { return new LogCategory("Debug"); } }
 public static LogCategory Info { get { return new LogCategory("Info"); } }
 public static LogCategory Warning { get { return new LogCategory("Warning"); } }
 public static LogCategory Error { get { return new LogCategory("Error"); } }
}

将类型安全的字符串值作为参数传递:

public static void Write(string message, LogCategory logCategory)
{
   var log = new LogEntry { Message = message };
   Logger.Write(log, logCategory.Value);
}

用法:

Logger.Write("This is almost like an enum.", LogCategory.Info);

6
投票

处理问题的另一种方法是使用枚举和字符串数组,将枚举值与字符串列表进行映射:

public enum GroupTypes
{
    TheGroup  = 0,
    TheOtherGroup 
}

string[] GroupTypesStr = {
    "OEM",
    "CMB"
};

你可以使用这样的东西:

Log.Write(GroupTypesStr[(int)GroupTypes.TheOtherGroup]);

它会提示CMB

优点:

  1. 简单而干净的代码。
  2. 高性能(特别是与那些使用类的方法相比)

缺点:

  1. 在编辑时容易搞乱列表,但是对于一个简短的列表是可以的。

4
投票

您是否考虑使用词典查找表?

enum GroupTypes
{
    TheGroup,
    TheOtherGroup
}

Dictionary<string, GroupTypes> GroupTypeLookup = new Dictionary<string, GroupTypes>();
// initialize lookup table:
GroupTypeLookup.Add("OEM", TheGroup);
GroupTypeLookup.Add("CMB", TheOtherGroup);

然后,您可以使用GroupTypeLookup.TryGetValue()在读取时查找字符串。


3
投票

C#不支持枚举字符串,但在大多数情况下,您可以使用List或Dictionary来获得所需的效果。

例如。要打印通过/未通过结果:

List<string> PassFail = new List<string> { "FAIL", "PASS" };
bool result = true;
Console.WriteLine("Test1: " + PassFail[result.GetHashCode()]);

2
投票

我会把它变成一个完全避免枚举的类。然后使用类型处理程序,您可以在从数据库中获取对象时创建该对象。

IE:

public class Group
{
    public string Value{ get; set; }
    public Group( string value ){ Value = value; } 
    public static Group TheGroup() { return new Group("OEM"); }
    public static Group OtherGroup() { return new Group("CMB"); }

}

2
投票

我只想创建一个字典并使用代码作为密钥。

编辑:要解决有关进行反向查找(找到密钥)的注释,这将不会非常有效。如果有必要,我会编写一个新类来处理它。


2
投票

我的第一个问题 - 您是否可以访问数据库本身?这应该在数据库中标准化,理想情况下,否则,任何解决方案都将容易出错。根据我的经验,充满“OEM”和“CMB”的数据字段往往随着时间的推移而混合“oem”和其他“废话数据”之类的东西....如果你可以将其标准化,你可以使用密钥在包含元素作为您的Enum的表中,您已经完成了,结构更加清晰。

如果那不可用,我会制作你的Enum,并创建一个类来为你解析你的字符串到Enum。与使用Enum.Parse / Reflection / etc的任何变通方法相比,这至少可以为您提供处理非标准条目的灵活性以及陷阱或处理错误的更大灵活性。字典可以工作,但如果您遇到案例问题,可能会发生故障等。

我建议写一堂课,这样你就可以做:

// I renamed this to GroupType, since it sounds like each element has a single type...
GroupType theType = GroupTypeParser.GetGroupType(theDBString);

这样可以保留大部分可读性,而无需更改数据库。


2
投票

如果我理解正确,您需要从字符串转换为枚举:

enum GroupTypes {
    Unknown = 0,
    OEM = 1,
    CMB = 2
}
static GroupTypes StrToEnum(string str){
    GroupTypes g = GroupTypes.Unknown;
    try {
        object o = Enum.Parse(typeof(GroupTypes), str, true);
        g = (GroupTypes)(o ?? 0);
    } catch {
    }
    return g;
}
// then use it like this
GroupTypes g1 = StrToEnum("OEM");
GroupTypes g2 = StrToEnum("bad value");

如果您愿意,可以使用enum类型的泛型更加花哨。


2
投票

对Glennular Extension方法的一个小调整,所以你可以在其他东西上使用扩展而不仅仅是ENUM;

using System;
using System.ComponentModel;
namespace Extensions {
    public static class T_Extensions {
        /// <summary>
        /// Gets the Description Attribute Value
        /// </summary>
        /// <typeparam name="T">Entity Type</typeparam>
        /// <param name="val">Variable</param>
        /// <returns>The value of the Description Attribute or an Empty String</returns>
        public static string Description<T>(this T t) {
            DescriptionAttribute[] attributes = (DescriptionAttribute[])t.GetType().GetField(t.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return attributes.Length > 0 ? attributes[0].Description : string.Empty;
        }
    }
}

或者使用Linq

using System;
using System.ComponentModel;
using System.Linq;

namespace Extensions {


public static class T_Extensions {
        public static string Description<T>(this T t) =>
            ((DescriptionAttribute[])t
            ?.GetType()
            ?.GetField(t?.ToString())
            ?.GetCustomAttributes(typeof(DescriptionAttribute), false))
            ?.Select(a => a?.Description)
            ?.FirstOrDefault() 
            ?? string.Empty;  
    }
}

2
投票
public class DataType
{
    private readonly string value;
    private static readonly Dictionary<string, DataType> predefinedValues;

    public static readonly DataType Json = new DataType("json");
    public static readonly DataType Xml = new DataType("xml");
    public static readonly DataType Text = new DataType("text");
    public static readonly DataType Html = new DataType("html");
    public static readonly DataType Binary = new DataType("binary");

    static DataType()
    {
        predefinedValues = new Dictionary<string, DataType>();
        predefinedValues.Add(Json.Value, Json);
        predefinedValues.Add(Xml.Value, Xml);
        predefinedValues.Add(Text.Value, Text);
        predefinedValues.Add(Html.Value, Html);
        predefinedValues.Add(Binary.Value, Binary);
    }

    private DataType(string value)
    {
        this.value = value;
    }

    public static DataType Parse(string value)
    {
        var exception = new FormatException($"Invalid value for type {nameof(DataType)}");
        if (string.IsNullOrEmpty(value))
            throw exception;

        string key = value.ToLower();
        if (!predefinedValues.ContainsKey(key))
            throw exception;

        return predefinedValues[key];
    }

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

1
投票

我甚至按照@Even(通过class Xpublic static X成员)的建议实现了一些枚举,只是为了稍后发现这些天,从.Net 4.5开始,有正确的ToString()方法。

现在我重新将所有内容重新实现回枚举。


145
投票

您还可以使用扩展模型:

public enum MyEnum
{
    [Description("String 1")]
    V1= 1,
    [Description("String 2")]
    V2= 2
} 

您的扩展类

public static class MyEnumExtensions
{
    public static string ToDescriptionString(this MyEnum val)
    {
        DescriptionAttribute[] attributes = (DescriptionAttribute[])val
           .GetType()
           .GetField(val.ToString())
           .GetCustomAttributes(typeof(DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : string.Empty;
    }
} 

用法:

MyEnum myLocal = MyEnum.V1;
print(myLocal.ToDescriptionString());

1
投票

在VS 2015中,您可以使用nameof

public class LogCategory
{
    public static string Trace;
    public static string Debug;
    public static string Info;
    public static string Warning;
    public static string Error;
}

用法:

Logger.Write("This is almost like an enum.", nameof(LogCategory.Info));

1
投票

这是一种将它用作强类型参数或字符串的方法:

public class ClassLikeEnum
{
    public string Value
    {
        get;
        private set;
    }

    ClassLikeEnum(string value) 
    {
        Value = value;
    }

    public static implicit operator string(ClassLikeEnum c)
    {
        return c.Value;
    }

    public static readonly ClassLikeEnum C1 = new ClassLikeEnum("RandomString1");
    public static readonly ClassLikeEnum C2 = new ClassLikeEnum("RandomString2");
}

1
投票

您可以使用两个枚举。一个用于数据库,另一个用于可读性。

你只需要确保它们保持同步,这似乎是一个很小的成本。您不必设置值,只需将位置设置为相同,但设置值可以非常清楚两个枚举相关并防止错误重新排列枚举成员。评论让维护人员知道这些是相关的,必须保持同步。

// keep in sync with GroupTypes
public enum GroupTypeCodes
{
    OEM,
    CMB
}

// keep in sync with GroupTypesCodes
public enum GroupTypes
{
    TheGroup = GroupTypeCodes.OEM,
    TheOtherGroup = GroupTypeCodes.CMB
}

要使用它,您只需先转换为代码:

GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = ((GroupTypeCodes)myGroupType).ToString();

然后,如果您想使它更方便,您可以添加仅适用于此类枚举的扩展函数:

public static string ToString(this GroupTypes source)
{
    return ((GroupTypeCodes)source).ToString();
}

然后你可以这样做:

GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = myGroupType.ToString();

1
投票

我基本上是在寻找@ArthurC的Reflection回答

只是为了扩展他的答案,你可以通过一个通用函数使它变得更好:

    // If you want for a specific Enum
    private static string EnumStringValue(GroupTypes e)
    {
        return EnumStringValue<GroupTypes>(e);
    }

    // Generic
    private static string EnumStringValue<T>(T enumInstance)
    {
        return Enum.GetName(typeof(T), enumInstance);
    } 

然后你可以包装你拥有的任何东西

EnumStringValue(GroupTypes.TheGroup) // if you incorporate the top part

要么

EnumStringValue<GroupTypes>(GroupTypes.TheGroup) // if you just use the generic

0
投票

根据其他意见,这是我想出的。这种方法避免了在想要获取常量值的地方键入.Value。

我有这样的所有字符串枚举的基类:

using System;
using Newtonsoft.Json;

[JsonConverter(typeof(ConstantConverter))]
public class StringEnum: IConvertible
{
    public string Value { get; set; }

    protected StringEnum(string value)
    {
        Value = value;
    }

    public static implicit operator string(StringEnum c)
    {
        return c.Value;
    }
    public string ToString(IFormatProvider provider)
    {
        return Value;
    }

    public TypeCode GetTypeCode()
    {
        throw new NotImplementedException();
    }

    public bool ToBoolean(IFormatProvider provider)
    {
        throw new NotImplementedException();
    }
    //The same for all the rest of IConvertible methods
}

JsonConverter是这样的:

using System;
using Newtonsoft.Json;

class ConstantConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
        {
            serializer.Serialize(writer, null);
        }
        else
        {
            serializer.Serialize(writer, value.ToString());
        }
    }
}

实际的字符串枚举将是这样的:

public sealed class Colors : StringEnum
{
    public static Colors Red { get { return new Catalog("Red"); } }
    public static Colors Yellow { get { return new Catalog("Yellow"); } }
    public static Colors White { get { return new Catalog("White"); } }

    private Colors(string value) : base(value) { }
}

有了这个,您可以使用Color.Red甚至序列化为json而不使用Value属性


0
投票

我不需要像在属性中存储字符串那样强大的东西。我只需要将像MyEnum.BillEveryWeek这样的东西变成“每周一次的法案”或MyEnum.UseLegacySystem变成“使用遗留系统” - 基本上将它的骆驼套管分为单个小写单词。

public static string UnCamelCase(this Enum input, string delimiter = " ", bool preserveCasing = false)
{
    var characters = input.ToString().Select((x, i) =>
    {

       if (i > 0 && char.IsUpper(x))
       {
           return delimiter + x.ToString(CultureInfo.InvariantCulture);
       }
       return x.ToString(CultureInfo.InvariantCulture);

    });

    var result = preserveCasing
       ? string.Concat(characters)
       : string.Concat(characters).ToLower();

    var lastComma = result.LastIndexOf(", ", StringComparison.Ordinal);

    if (lastComma > -1)
    {
       result = result.Remove(lastComma, 2).Insert(lastComma, " and ");
    }

    return result;
}

MyEnum.UseLegacySystem.UnCamelCase()输出“使用遗留系统”

如果你设置了多个标志,它会将其转换为普通英语(逗号分隔,除了“和”代替最后一个逗号)。

var myCustomerBehaviour = MyEnum.BillEveryWeek | MyEnum.UseLegacySystem | MyEnum.ChargeTaxes;

Console.WriteLine(myCustomerBehaviour.UnCamelCase());
//outputs "bill every week, use legacy system and charge taxes"

0
投票

我做过这样的事情;

public enum BusinessUnits
{
    NEW_EQUIPMENT = 0,
    USED_EQUIPMENT = 1,
    RENTAL_EQUIPMENT = 2,
    PARTS = 3,
    SERVICE = 4,
    OPERATOR_TRAINING = 5
}

public class BusinessUnitService
{
    public static string StringBusinessUnits(BusinessUnits BU)
    {
        switch (BU)
        {
            case BusinessUnits.NEW_EQUIPMENT: return "NEW EQUIPMENT";
            case BusinessUnits.USED_EQUIPMENT: return "USED EQUIPMENT";
            case BusinessUnits.RENTAL_EQUIPMENT: return "RENTAL EQUIPMENT";
            case BusinessUnits.PARTS: return "PARTS";
            case BusinessUnits.SERVICE: return "SERVICE";
            case BusinessUnits.OPERATOR_TRAINING: return "OPERATOR TRAINING";
            default: return String.Empty;
        }
    }
}

称之为;

BusinessUnitService.StringBusinessUnits(BusinessUnits.PARTS)

0
投票

我想完全避免使用字符串文字,而且我也不需要在项目描述中有空格。更重要的是,我希望有一种机制来检查提供的字符串是否是有效的项目,所以我提出了这个解决方案:

public class Seasons
{
    public static string Spring { get; }
    public static string Summer { get; }
    public static string Fall { get; }
    public static string Winter { get; }

    public static bool IsValid(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            return false;
        }

        try
        {           
            return typeof(Seasons).GetProperty(propertyName) != null;
        }
        catch
        {
            return false;
        }       
    }
}

以下是它的工作原理:

void Main()
{
    string s = nameof(Seasons.Fall);
    Console.WriteLine($"Fall is valid: {Seasons.IsValid(s)}"); // true

    s = "WrongSeason";
    Console.WriteLine($"WrongSeason is valid: {Seasons.IsValid(s)}"); // false
}

我试图将IsValid()重构为基类并使用反射来读取类型(MethodBase.GetCurrentMethod()。DeclaringType),但由于我想让它静态,它返回基类类型,而不是继承类型。您的补救措施将非常受欢迎!这是我想要实现的目标:

public  class Seasons : ConstantStringsBase
{
    // ... same
}

public  class ConstantStringsBase
{
    public static bool IsValid(string propertyName)
    {       
        return MethodBase.GetCurrentMethod().DeclaringType.GetProperty(propertyName) != null;
    }
}

0
投票

按照@Even Mien的回答,我试图更进一步,使它成为通用,我似乎几乎在那里,但一个案例仍然抵制,我可能可以简化我的代码。 我在这里发布,如果有人看到我如何改进,特别是让它工作,因为我不能从字符串中分配它

到目前为止,我有以下结果:

        Console.WriteLine(TestEnum.Test1);//displays "TEST1"

        bool test = "TEST1" == TestEnum.Test1; //true

        var test2 = TestEnum.Test1; //is TestEnum and has value

        string test3 = TestEnum.Test1; //test3 = "TEST1"

        var test4 = TestEnum.Test1 == TestEnum.Test2; //false
         EnumType<TestEnum> test5 = "TEST1"; //works fine

        //TestEnum test5 = "string"; DOESN'T compile .... :(:(

奇迹发生的地方 :

public abstract  class EnumType<T>  where T : EnumType<T>   
{

    public  string Value { get; set; }

    protected EnumType(string value)
    {
        Value = value;
    }


    public static implicit operator EnumType<T>(string s)
    {
        if (All.Any(dt => dt.Value == s))
        {
            Type t = typeof(T);

            ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,null, new Type[] { typeof(string) }, null);

            return (T)ci.Invoke(new object[] {s});
        }
        else
        {
            return null;
        }
    }

    public static implicit operator string(EnumType<T> dt)
    {
        return dt?.Value;
    }


    public static bool operator ==(EnumType<T> ct1, EnumType<T> ct2)
    {
        return (string)ct1 == (string)ct2;
    }

    public static bool operator !=(EnumType<T> ct1, EnumType<T> ct2)
    {
        return !(ct1 == ct2);
    }


    public override bool Equals(object obj)
    {
        try
        {
            return (string)obj == Value;
        }
        catch
        {
            return false;
        }
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public static IEnumerable<T> All
     => typeof(T).GetProperties()
       .Where(p => p.PropertyType == typeof(T))
       .Select(x => (T)x.GetValue(null, null));



}

我只需要为我的枚举声明这个:

public class TestEnum : EnumType<TestEnum> 
{

    private TestEnum(string value) : base(value)
    {}

    public static TestEnum Test1 { get { return new TestEnum("TEST1"); } }
    public static TestEnum Test2 { get { return new TestEnum("TEST2"); } }
}

0
投票

取自@EvenMien并添加了一些评论。 (也适用于我自己的用例)

public struct AgentAction
{
    private AgentAction(string value) { Value = value; }

    public string Value { get; private set; }

    public override string ToString()
    {
        return this.Value;
    }

    public static AgentAction Login = new AgentAction("Logout");
    public static AgentAction Logout = new AgentAction("Logout");

    public static implicit operator string(AgentAction action) { return action.ToString(); }
}

78
投票

如何使用带常量的静态类?客户端代码看起来与枚举没有什么不同。

static class GroupTypes
{
  public const string TheGroup = "OEM";
  public const string TheOtherGroup = "CMB";
}

void DoSomething(GroupTypes groupType)
{
  if(groupType == GroupTypes.TheOtherGroup)
  {
    //Launch nuclear bomb 
  }  
}

28
投票

您可以向枚举中的项添加属性,然后使用反射从属性中获取值。

您必须使用“field”说明符来应用属性,如下所示:

enum GroupTypes
{
    [field:Description("OEM")]
    TheGroup,

    [field:Description("CMB")]
    TheOtherGroup
}

然后,您将反映枚举类型的静态字段(在本例中为GroupTypes),并使用反射获取您要查找的值的DescriptionAttribute

public static DescriptionAttribute GetEnumDescriptionAttribute<T>(
    this T value) where T : struct
{
    // The type of the enum, it will be reused.
    Type type = typeof(T);

    // If T is not an enum, get out.
    if (!type.IsEnum) 
        throw new InvalidOperationException(
            "The type parameter T must be an enum type.");

    // If the value isn't defined throw an exception.
    if (!Enum.IsDefined(type, value))
        throw new InvalidEnumArgumentException(
            "value", Convert.ToInt32(value), type);

    // Get the static field for the value.
    FieldInfo fi = type.GetField(value.ToString(), 
        BindingFlags.Static | BindingFlags.Public);

    // Get the description attribute, if there is one.
    return fi.GetCustomAttributes(typeof(DescriptionAttribute), true).
        Cast<DescriptionAttribute>().SingleOrDefault();
}

如果您希望能够确定属性是否应用,我选择返回上面的DescriptionAttribute


19
投票

你可以很容易地做到这一点。使用以下代码。

enum GroupTypes
{
   OEM,
   CMB
};

然后,当您想要获取每个枚举元素的字符串值时,只需使用以下代码行。

String oemString = Enum.GetName(typeof(GroupTypes), GroupTypes.OEM);

我以前成功地使用过这个方法,并且我还使用了一个常量类来保存​​字符串常量,两者都运行得很好,但我更喜欢这个。


13
投票

为包含以下内容的数据库创建第二个枚举:

enum DBGroupTypes
{
    OEM = 0,
    CMB = 1
}

现在,您可以使用Enum.Parse从字符串“OEM”和“CMB”中检索正确的DBGroupTypes值。然后,您可以将这些转换为int,并从您希望在模型中进一步使用的右枚举中检索正确的值。


11
投票

使用课程。

编辑:更好的例子

class StarshipType
{
    private string _Name;
    private static List<StarshipType> _StarshipTypes = new List<StarshipType>();

    public static readonly StarshipType Ultralight = new StarshipType("Ultralight");
    public static readonly StarshipType Light = new StarshipType("Light");
    public static readonly StarshipType Mediumweight = new StarshipType("Mediumweight");
    public static readonly StarshipType Heavy = new StarshipType("Heavy");
    public static readonly StarshipType Superheavy = new StarshipType("Superheavy");

    public string Name
    {
        get { return _Name; }
        private set { _Name = value; }
    }

    public static IList<StarshipType> StarshipTypes
    {
        get { return _StarshipTypes; }
    }

    private StarshipType(string name, int systemRatio)
    {
        Name = name;
        _StarshipTypes.Add(this);
    }

    public static StarshipType Parse(string toParse)
    {
        foreach (StarshipType s in StarshipTypes)
        {
            if (toParse == s.Name)
                return s;
        }
        throw new FormatException("Could not parse string.");
    }
}

8
投票

尝试将常量添加到静态类。你没有最终得到一个类型,但你有可读的,有组织的常量:

public static class GroupTypes
{
    public const string TheGroup = "OEM";
    public const string TheOtherGroup = "CMB"
}

6
投票

这是我用来将枚举值作为字符串的扩展方法。首先是枚举。

public enum DatabaseEnvironment
{
    [Description("AzamSharpBlogDevDatabase")]
    Development = 1, 
    [Description("AzamSharpBlogQADatabase")]
    QualityAssurance = 2, 
    [Description("AzamSharpBlogTestDatabase")] 
    Test = 3
}

Description属性来自System.ComponentModel。

这是我的扩展方法:

public static string GetValueAsString(this DatabaseEnvironment environment) 
{
    // get the field 
    var field = environment.GetType().GetField(environment.ToString());
    var customAttributes = field.GetCustomAttributes(typeof (DescriptionAttribute), false);

    if(customAttributes.Length > 0)
    {
        return (customAttributes[0] as DescriptionAttribute).Description;  
    }
    else
    {
        return environment.ToString(); 
    }
}

现在,您可以使用以下代码访问枚举作为字符串值:

[TestFixture]
public class when_getting_value_of_enum
{
    [Test]
    public void should_get_the_value_as_string()
    {
        Assert.AreEqual("AzamSharpBlogTestDatabase",DatabaseEnvironment.Test.GetValueAsString());  
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.