Asociar enumeraciones con cadenas en C #

Sé que lo siguiente no es posible porque tiene que ser una int

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

De mi base de datos obtengo un campo con códigos incomprensibles (OEM y CMB). Me gustaría hacer de este campo una enumeración o algo comprensible. Debido a que el objective es legibilidad, la solución debe ser concisa.
¿Qué otras opciones tengo?

Me gusta usar propiedades en una clase en lugar de métodos, ya que se ven más como enum.

Aquí hay un ejemplo para un registrador:

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

Pase los valores de cadena de tipo seguro como un parámetro:

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

Uso:

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

También puedes usar el modelo de extensión:

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

Su clase de extensión

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

uso:

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

¿Qué hay de usar una clase estática con constantes? El código del cliente no se verá diferente de las enumeraciones.

 static class GroupTypes { public const string TheGroup = "OEM"; public const string TheOtherGroup = "CMB"; } void DoSomething(GroupTypes groupType) { if(groupType == GroupTypes.TheOtherGroup) { //Launch nuclear bomb } } 

Puede agregar atributos a los elementos en la enumeración y luego usar la reflexión para obtener los valores de los atributos.

Tendría que usar el especificador de “campo” para aplicar los atributos, así:

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

A continuación, reflexionaría sobre los campos estáticos del tipo de enumeración (en este caso, GroupTypes) y obtendría el atributo DescriptionAttribute para el valor que estaba buscando mediante la reflexión:

 public static DescriptionAttribute GetEnumDescriptionAttribute( 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().SingleOrDefault(); } 

Opté por devolver el atributo DescriptionAttribute anteriormente, en caso de que quiera poder determinar si el atributo se aplicó o no.

Puedes hacerlo muy fácilmente en realidad. Usa el siguiente código

 enum GroupTypes { OEM, CMB }; 

Luego, cuando desee obtener el valor de cadena de cada elemento enum, solo use la siguiente línea de código.

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

He usado este método con éxito en el pasado, y también he usado una clase de constantes para mantener constantes de cadena, ambos funcionan bastante bien, pero tiendo a preferir esto.

Cree una segunda enumeración para su DB que contenga lo siguiente:

 enum DBGroupTypes { OEM = 0, CMB = 1 } 

Ahora puede usar Enum.Parse para recuperar el valor DBGroupTypes correcto de las cadenas “OEM” y “CMB”. A continuación, puede convertirlos a int y recuperar los valores correctos de la enumeración correcta que desea usar en su modelo.

Usa una clase

Editar: mejor ejemplo

 class StarshipType { private string _Name; private static List _StarshipTypes = new List(); 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 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."); } } 

Intente agregar constantes a una clase estática. No terminas con un tipo, pero tienes constantes organizadas y legibles:

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

Aquí está el método de extensión que usé para obtener el valor enum como cadena. Primero aquí está la enumeración.

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

El atributo Descripción vino de System.ComponentModel.

Y aquí está mi método de extensión:

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

Ahora puede acceder a la enumeración como valor de cadena utilizando el siguiente código:

 [TestFixture] public class when_getting_value_of_enum { [Test] public void should_get_the_value_as_string() { Assert.AreEqual("AzamSharpBlogTestDatabase",DatabaseEnvironment.Test.GetValueAsString()); } } 

Otra forma de resolver el problema es tener una enumeración y una matriz de cadenas que correlacionarán los valores enum con la lista de cadenas:

 public enum GroupTypes { TheGroup = 0, TheOtherGroup } string[] GroupTypesStr = { "OEM", "CMB" }; 

puedes usar algo como esto:

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

Indicará CMB

PROS:

  1. Código fácil y limpio.
  2. Alto rendimiento (especialmente en comparación con aquellos enfoques que usan clases)

CONTRAS:

  1. Propenso a arruinar la lista cuando la edita, pero estará bien para una lista corta.

¿Has considerado una tabla de búsqueda con un diccionario?

 enum GroupTypes { TheGroup, TheOtherGroup } Dictionary GroupTypeLookup = new Dictionary(); // initialize lookup table: GroupTypeLookup.Add("OEM", TheGroup); GroupTypeLookup.Add("CMB", TheOtherGroup); 

Luego puede usar GroupTypeLookup.TryGetValue () para buscar una cadena cuando la lea.

Respuesta de Even:

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

Solo quería agregar una forma de imitar el cambio con enumeraciones basadas en clases:

 public void Foo(LogCategory logCategory){ var @switch = new Dictionary{ {LogCategory.Trace, ()=>Console.Writeline("Trace selected!")}, {LogCategory.Debug, ()=>Console.Writeline("Debug selected!")}, {LogCategory.Error, ()=>Console.Writeline("Error selected!")}}; //will print one of the line based on passed argument @switch[logCategory](); } 

C # no admite cadenas enumeradas, pero para la mayoría de las situaciones puede usar una Lista o Diccionario para obtener el efecto deseado.

Por ejemplo, para imprimir resultados de aprobado / reprobado:

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

Lo convertiría en una clase y evitaría una enumeración por completo. Y luego, con el uso de un manipulador de líneas, puede crear el objeto cuando lo toma del DB.

ES DECIR:

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

Simplemente crearía un diccionario y usaría el código como la clave.

Editar: para abordar el comentario sobre hacer una búsqueda inversa (encontrar la clave), esto no sería terriblemente eficiente. Si esto es necesario, escribiría una nueva clase para manejarlo.

Mi primera pregunta: ¿tienes acceso a la base de datos? Esto debería normalizarse en la base de datos, idealmente, de lo contrario, cualquier solución será propensa al error. En mi experiencia, los campos de datos llenos de “OEM” y “CMB” tienden a tener cosas como “oem” y otros “datos basura” mezclados a lo largo del tiempo … Si puede normalizarlo, puede usar la clave en la tabla que contiene los elementos como tu Enum, y listo, con una estructura mucho más limpia.

Si no está disponible, haré tu Enum y haré una clase para analizar tu cadena en el Enum por ti. Esto al menos le daría cierta flexibilidad en el manejo de entradas no estándar y mucha más flexibilidad para capturar o manejar errores que hacer cualquiera de las soluciones usando Enum.Parse / Reflection / etc. Un diccionario funcionaría, pero podría descomponerse si alguna vez tiene problemas de caso, etc.

Te recomendaría escribir una clase para que puedas hacer:

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

Esto conserva la mayor parte de su legibilidad sin tener que cambiar la base de datos.

Si entiendo correctamente, necesita una conversión de cadena a enumeración:

 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"); 

Puede hacerlo más elegante con generics para el tipo enum si lo desea.

En VS 2015, puede usar el nombre de

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

Uso:

 Logger.Write("This is almost like an enum.", nameof(LogCategory.Info)); 
 public class DataType { private readonly string value; private static readonly Dictionary 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(); 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; } } } 

Si intenta hacer que su código sea legible:

 class GroupTypes { public static final String (whatever oem stands for) = "OEM"; public static final String (whatever cmb stands for) = "CMB"; ... } 

y si necesita una lista de ellos, incluya estos finales en una lista final estática . Este ejemplo está en Java.

Si intenta hacer que su aplicación sea legible, agregue:

 public static final Map groupsByDbValue; static { groupsByDbValue = new HashMap(); groupsByDbValue.put("OEM", "(whatever OEM stands for)"); groupsByDbValue.put("CMB", "(whatever CMB stands for)"); } 

las enumeraciones en C # están restringidas a tipos numéricos enteros subyacentes (byte, sbyte, short, ushort, int, uint, long y ulong). No puede asociarlos con un carácter o valor subyacente basado en cadena.

Un enfoque diferente podría ser definir un diccionario de tipo Diccionario .

Esta es una forma de usarlo como un parámetro fuertemente tipado o como una cadena :

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

Puedes usar dos enums. Uno para la base de datos y el otro para la legibilidad.

Solo necesita asegurarse de que se mantengan sincronizados, lo que parece ser un pequeño costo. No tiene que establecer los valores, simplemente configure las posiciones de la misma manera, pero al establecer los valores queda muy claro que las dos enumeraciones están relacionadas y evita que los errores reorganicen los miembros enum. Y un comentario le permite al personal de mantenimiento saber que están relacionados y deben mantenerse sincronizados.

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

Para usarlo solo debes convertir al código primero:

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

Luego, si desea hacerlo aún más conveniente, puede agregar una función de extensión que solo funciona para este tipo de enumeración:

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

y luego puedes hacer lo siguiente:

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

Un pequeño ajuste al método de Extensión Glennular, por lo que podría usar la extensión en otras cosas aparte de solo ENUM;

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

O utilizando Linq

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

Básicamente estaba buscando la respuesta de Reflexión de @ArthurC

Solo para extender su respuesta un poco, puedes hacerlo aún mejor teniendo una función genérica:

  // If you want for a specific Enum private static string EnumStringValue(GroupTypes e) { return EnumStringValue(e); } // Generic private static string EnumStringValue(T enumInstance) { return Enum.GetName(typeof(T), enumInstance); } 

Entonces puedes simplemente envolver lo que tengas

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

o

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

Basado en otras opiniones, esto es lo que se me ocurrió. Este enfoque evita tener que escribir .Valor donde desea obtener el valor constante.

Tengo una clase base para todas las enumeraciones de cadenas como esta:

 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 } 

El JsonConverter es así:

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

Y una cadena de enumeración real será algo como esto:

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

Y con esto, puede usar Color.Red para incluso serializar a json sin usar la propiedad Value

Incluso implementé algunas enumeraciones según lo sugerido por @Even (a través de class X miembros class X y public static X ), solo para descubrir más tarde que, a partir de .Net 4.5, existe el método ToString() correcto .

Ahora estoy volviendo a implementar todo en enumeraciones.

No necesitaba nada tan sólido como guardar la cadena en atributos. Solo necesitaba convertir algo como MyEnum.BillEveryWeek en “bill MyEnum.BillEveryWeek week” o MyEnum.UseLegacySystem en “usar el sistema heredado”, básicamente dividir la enumeración por su camel-casing en palabras individuales en minúsculas.

 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() genera “uso del sistema heredado”

Si tiene varios indicadores configurados, lo convertirá en inglés simple (delimitado por comas, excepto un “y” en lugar de la última coma).

 var myCustomerBehaviour = MyEnum.BillEveryWeek | MyEnum.UseLegacySystem | MyEnum.ChargeTaxes; Console.WriteLine(myCustomerBehaviour.UnCamelCase()); //outputs "bill every week, use legacy system and charge taxes" 

He hecho algo como esto;

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

Llámalo con esto;

 BusinessUnitService.StringBusinessUnits(BusinessUnits.PARTS) 

¿Por qué no usar ToString () en los tipos Enum?

 private enum LogType { Error, Warning, Info}; private WriteLog(string message, LogType logType) { Log.Write(message, logType.ToString()); }