¿Cómo definir una enumeración con valor de cadena?


97

Estoy tratando de definir Enumy agregar separadores comunes válidos que se usan en archivos CSV o similares. Luego, lo vincularé a ComboBoxcomo fuente de datos, por lo que cada vez que agregue o elimine de la definición de Enum, no necesitaría cambiar nada en el cuadro combinado.

El problema es cómo puedo definir enum con representación de cadena, algo como:

public enum SeparatorChars{Comma = ",", Tab = "\t", Space = " "}


Respuestas:


113

No puede: los valores de enumeración deben ser valores integrales. Puede usar atributos para asociar un valor de cadena con cada valor de enumeración, o en este caso, si cada separador es un solo carácter, puede usar el charvalor:

enum Separator
{
    Comma = ',',
    Tab = '\t',
    Space = ' '
}

(EDITAR: solo para aclarar, no puede hacer charel tipo subyacente de la enumeración, pero puede usar charconstantes para asignar el valor integral correspondiente a cada valor de enumeración. El tipo subyacente de la enumeración anterior es int).

Luego, un método de extensión si lo necesita:

public string ToSeparatorString(this Separator separator)
{
    // TODO: validation
    return ((char) separator).ToString();
}

Char no es válido en enumeraciones. Msdn: "Cada tipo de enumeración tiene un tipo subyacente, que puede ser cualquier tipo integral excepto char".
dowhilefor

8
@dowhilefor: Sin embargo, puede usar un literal char para el valor , según mi respuesta. Lo probé :)
Jon Skeet

como este requisito es para archivos, el usuario puede necesitar un separador CRLF. ¿Funcionará también para ese caso?
Maheep

Gracias Jon, ¡¿no cuenta como char ?!
Saeid Yazdani

1
@ShaunLuttin: las enumeraciones son solo "números con nombre", por lo que una enumeración de cadena realmente no encaja en ese modelo en absoluto.
Jon Skeet

83

Hasta donde yo sé, no se le permitirá asignar valores de cadena a enum. Lo que puede hacer es crear una clase con constantes de cadena en ella.

public static class SeparatorChars
{
    public static String Comma { get { return ",";} } 
    public static String Tab { get { return "\t,";} } 
    public static String Space { get { return " ";} } 
}

9
El lado negativo de este enfoque, opuesto a otros, es que no puede enumerarlos sin hacer algo extra / especial.
Caesay

Esto no ayuda a hacer cumplir ciertos valores durante el tiempo de compilación, ya separatorque ahora es una cadena (podría ser cualquier cosa) en lugar de un Separatortipo con valores válidos restringidos.
ChickenFeet

73

Puede lograrlo, pero requerirá un poco de trabajo.

  1. Defina una clase de atributo que contendrá el valor de cadena para enum.
  2. Defina un método de extensión que devolverá el valor del atributo. Por ejemplo, GetStringValue (este valor de Enum) devolverá el valor del atributo.
  3. Entonces puedes definir la enumeración así ...
prueba pública enum: int {
    [StringValue ("a")]
    Foo = 1,
    [StringValue ("b")]
    Algo = 2        
} 
  1. Para recuperar el valor de Attrinbute Test.Foo.GetStringValue ();

Consulte: Enum con valores de cadena en C #


5
Sé que esto es antiguo, pero obviamente es único y le permite usar enumeraciones en el código y el valor de cadena en la base de datos. Increíble
A_kat

1
Otro comentario tardío, pero esta es realmente una solución brillante
Alan

36

Para una enumeración simple de valores de cadena (o cualquier otro tipo):

public static class MyEnumClass
{
    public const string 
        MyValue1 = "My value 1",
        MyValue2 = "My value 2";
}

Uso: string MyValue = MyEnumClass.MyValue1;


1
Si bien esto no es una enumeración, creo que esto podría proporcionar la mejor solución a lo que el usuario está tratando de hacer. A veces, la solución más simple es la mejor.
Zesty

29

No puede hacer esto con enumeraciones, pero puede hacerlo así:

public static class SeparatorChars
{
    public static string Comma = ",";

    public static string Tab = "\t";

    public static string Space = " ";
}

1
+1 Aunque creo que es la solución correcta, cambiaría el nombre de la clase o cambiaría el tipo a caracteres. Solo para ser consistente.
hasta el

Gracias, ¿puede decirnos cuál será el equivalente comboBox.DataSource = Enum.GetValues(typeof(myEnum));en este caso?
Saeid Yazdani

1
@ Sean87: Si quieres tener eso, aceptaría la respuesta de JonSkeets.
Fischermaen

Creo que esta es casi la respuesta correcta, porque no se puede usar dentro de switch-casebloques. Los campos deben estar consten orden. Pero aún así no se puede evitar si lo desea Enum.GetValues(typeof(myEnum)).
André Santaló

7
Usaría en constlugar de static. Las constantes son de solo lectura, así como estáticas y no se pueden asignar en constructores (a menos que sean campos de solo lectura).
Olivier Jacot-Descombes

12

No puede, porque enum solo puede basarse en un tipo numérico primitivo. Podría intentar usar un Dictionaryen su lugar:

Dictionary<String, char> separators = new Dictionary<string, char>
{
    {"Comma", ','}, 
    {"Tab",  '\t'}, 
    {"Space", ' '},
};

Alternativamente, puede usar un Dictionary<Separator, char>o Dictionary<Separator, string>where Separatores una enumeración normal:

enum Separator
{
    Comma,
    Tab,
    Space
}

lo que sería un poco más agradable que manipular las cuerdas directamente.


11

Una clase que emula el comportamiento de enumeración pero usando en stringlugar de intse puede crear de la siguiente manera ...

public class GrainType
{
    private string _typeKeyWord;

    private GrainType(string typeKeyWord)
    {
        _typeKeyWord = typeKeyWord;
    }

    public override string ToString()
    {
        return _typeKeyWord;
    }

    public static GrainType Wheat = new GrainType("GT_WHEAT");
    public static GrainType Corn = new GrainType("GT_CORN");
    public static GrainType Rice = new GrainType("GT_RICE");
    public static GrainType Barley = new GrainType("GT_BARLEY");

}

Uso...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

luego...

public void PrintGrainKeyword(GrainType grain) 
{
    Console.Writeline("My Grain code is " + grain.ToString());   // Displays "My Grain code is GT_WHEAT"
}

Lo único es que no puedes hacer GrainType myGrain = "GT_CORN", por ejemplo.
colmde

podría hacerlo si ignora al operador
SSX-SL33PY

8

Es un poco tarde para responder, pero tal vez ayude a alguien en el futuro. Me resultó más fácil usar la estructura para este tipo de problema.

La siguiente muestra es una copia pegada del código MS:

namespace System.IdentityModel.Tokens.Jwt
{
    //
    // Summary:
    //     List of registered claims from different sources http://tools.ietf.org/html/rfc7519#section-4
    //     http://openid.net/specs/openid-connect-core-1_0.html#IDToken
    public struct JwtRegisteredClaimNames
    {
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Actort = "actort";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Typ = "typ";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Sub = "sub";
        //
        // Summary:
        //     http://openid.net/specs/openid-connect-frontchannel-1_0.html#OPLogout
        public const string Sid = "sid";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Prn = "prn";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nbf = "nbf";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nonce = "nonce";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string NameId = "nameid";

    }
}

¿Podría explicar por qué este enfoque es mejor que usar una clase?
Gerardo Grignoli

@GerardoGrignoli No sé exactamente por qué usan struct en lugar de class en MS para este tipo de cosas. Ni siquiera traté de averiguarlo, ya que esto funciona perfectamente para mí. Tal vez intente hacer una pregunta aquí en la pila ...
suchoss

6

Tal vez sea demasiado tarde, pero aquí va.

Podemos usar el atributo EnumMember para administrar los valores de Enum.

public enum EUnitOfMeasure
{
    [EnumMember(Value = "KM")]
    Kilometer,
    [EnumMember(Value = "MI")]
    Miles
}

De esta forma, el valor de resultado para EUnitOfMeasure será KM o MI. Esto también se puede ver en la respuesta de Andrew Whitaker .


5

Para las personas que lleguen aquí en busca de una respuesta a una pregunta más genérica, puede extender el concepto de clase estática si desea que su código se vea como un enum.

El siguiente enfoque funciona cuando no ha finalizado lo enum namesque desea y enum valueses la stringrepresentación de enam name; utilícelo nameof()para simplificar la refactorización.

public static class Colours
{
    public static string Red => nameof(Red);
    public static string Green => nameof(Green);
    public static string Blue => nameof(Blue);
}

Esto logra la intención de una enumeración que tiene valores de cadena (como el siguiente pseudocódigo):

public enum Colours
{
    "Red",
    "Green",
    "Blue"
}

4

Creé una clase base para crear enumeraciones con valores de cadena en .NET. Es solo un archivo C # que puede copiar y pegar en sus proyectos, o instalar a través del paquete NuGet llamado StringEnum .

Uso:

///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = New("#FF0000");
    public static readonly HexColor Green = New("#00FF00");
    public static readonly HexColor Red = New("#000FF");
}

Caracteristicas

  • Su StringEnum se parece un poco a una enumeración normal:
    // Static Parse Method
    HexColor.Parse("#FF0000") // => HexColor.Red
    HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.Parse("invalid") // => throws InvalidOperationException

    // Static TryParse method.
    HexColor.TryParse("#FF0000") // => HexColor.Red
    HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.TryParse("invalid") // => null

    // Parse and TryParse returns the preexistent instances
    object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true

    // Conversion from your `StringEnum` to `string`
    string myString1 = HexColor.Red.ToString(); // => "#FF0000"
    string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)
  • Intellisense sugerirá el nombre de la enumeración si la clase está anotada con el comentario xml <completitionlist>. (Funciona tanto en C # como en VB): es decir

Demostración de Intellisense

Instalación

Ya sea:

  • Instale el último paquete StringEnum NuGet, que se basa en .Net Standard 1.0para que se ejecute en .Net Core> = 1.0, .Net Framework> = 4.5,Mono > = 4.6, etc.
  • O pegue la siguiente clase base StringEnum en su proyecto. ( última versión )
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static IList<T> valueList = new List<T>();
        protected static T New(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueList.Add(result);
            return result;
        }

        public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
        public override string ToString() => Value;

        public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
        public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;

        public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
        bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case sensitivity.</param>
        public static T Parse(string value, bool caseSensitive = false)
        {
            var result = TryParse(value, caseSensitive);
            if (result == null)
                throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");

            return result;
        }

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case sensitivity.</param>
        public static T TryParse(string value, bool caseSensitive = false)
        {
            if (value == null) return null;
            if (valueList.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            var field = valueList.FirstOrDefault(f => f.Value.Equals(value,
                    caseSensitive ? StringComparison.Ordinal
                                  : StringComparison.OrdinalIgnoreCase));
            // Not using InvariantCulture because it's only supported in NETStandard >= 2.0

            if (field == null)
                return null;

            return field;
        }
    }
  • Para Newtonsoft.Jsonsoporte de serialización, copie esta versión extendida en su lugar. StringEnum.cs

Me di cuenta después del hecho de que este código es similar a la respuesta de Ben. Sinceramente lo escribí desde cero. Sin embargo, creo que tiene algunos extras, como el <completitionlist>truco, la clase resultante se parece más a un Enum, sin uso de la reflexión sobre Parse (), el paquete NuGet y el repositorio donde, con suerte, abordaré los problemas entrantes y los comentarios.


3

Sobre la base de algunas de las respuestas aquí, he implementado una clase base reutilizable que imita el comportamiento de una enumeración pero con stringel tipo subyacente. Es compatible con varias operaciones que incluyen:

  1. obtener una lista de posibles valores
  2. convertir a cadena
  3. comparación con otras instancias a través de .Equals,== y!=
  4. conversión a / desde JSON usando un JSON.NET JsonConverter

Esta es la clase base en su totalidad:

public abstract class StringEnumBase<T> : IEquatable<T>
    where T : StringEnumBase<T>
{
    public string Value { get; }

    protected StringEnumBase(string value) => this.Value = value;

    public override string ToString() => this.Value;

    public static List<T> AsList()
    {
        return typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.Static)
            .Where(p => p.PropertyType == typeof(T))
            .Select(p => (T)p.GetValue(null))
            .ToList();
    }

    public static T Parse(string value)
    {
        List<T> all = AsList();

        if (!all.Any(a => a.Value == value))
            throw new InvalidOperationException($"\"{value}\" is not a valid value for the type {typeof(T).Name}");

        return all.Single(a => a.Value == value);
    }

    public bool Equals(T other)
    {
        if (other == null) return false;
        return this.Value == other?.Value;
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        if (obj is T other) return this.Equals(other);
        return false;
    }

    public override int GetHashCode() => this.Value.GetHashCode();

    public static bool operator ==(StringEnumBase<T> a, StringEnumBase<T> b) => a?.Equals(b) ?? false;

    public static bool operator !=(StringEnumBase<T> a, StringEnumBase<T> b) => !(a?.Equals(b) ?? false);

    public class JsonConverter<T> : Newtonsoft.Json.JsonConverter
        where T : StringEnumBase<T>
    {
        public override bool CanRead => true;

        public override bool CanWrite => true;

        public override bool CanConvert(Type objectType) => ImplementsGeneric(objectType, typeof(StringEnumBase<>));

        private static bool ImplementsGeneric(Type type, Type generic)
        {
            while (type != null)
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == generic)
                    return true;

                type = type.BaseType;
            }

            return false;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken item = JToken.Load(reader);
            string value = item.Value<string>();
            return StringEnumBase<T>.Parse(value);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value is StringEnumBase<T> v)
                JToken.FromObject(v.Value).WriteTo(writer);
        }
    }
}

Y así es como implementaría su "enumeración de cadena":

[JsonConverter(typeof(JsonConverter<Colour>))]
public class Colour : StringEnumBase<Colour>
{
    private Colour(string value) : base(value) { }

    public static Colour Red => new Colour("red");
    public static Colour Green => new Colour("green");
    public static Colour Blue => new Colour("blue");
}

Que podría usarse así:

public class Foo
{
    public Colour colour { get; }

    public Foo(Colour colour) => this.colour = colour;

    public bool Bar()
    {
        if (this.colour == Colour.Red || this.colour == Colour.Blue)
            return true;
        else
            return false;
    }
}

¡Espero que alguien encuentre esto útil!


2

Bueno, primero intenta asignar cadenas, no caracteres, incluso si son solo un carácter. use ',' en lugar de ",". Lo siguiente es que las enumeraciones solo aceptan tipos integrales sin charque pueda usar el valor Unicode, pero le recomiendo encarecidamente que no lo haga. Si está seguro de que estos valores siguen siendo los mismos, en diferentes culturas e idiomas, usaría una clase estática con cadenas constantes.


2

Si bien realmente no es posible utilizar charao astring como base para una enumeración, creo que esto no es lo que realmente te gusta hacer.

Como mencionó, le gustaría tener una enumeración de posibilidades y mostrar una representación de cadena de esto dentro de un cuadro combinado. Si el usuario selecciona una de estas representaciones de cadena, le gustaría obtener la enumeración correspondiente. Y esto es posible:

Primero tenemos que vincular alguna cadena a un valor de enumeración. Esto se puede hacer usando lo DescriptionAttributeque se describe aquí o aquí. .

Ahora necesita crear una lista de valores de enumeración y descripciones correspondientes. Esto se puede hacer mediante el siguiente método:

/// <summary>
/// Creates an List with all keys and values of a given Enum class
/// </summary>
/// <typeparam name="T">Must be derived from class Enum!</typeparam>
/// <returns>A list of KeyValuePair&lt;Enum, string&gt; with all available
/// names and values of the given Enum.</returns>
public static IList<KeyValuePair<T, string>> ToList<T>() where T : struct
{
    var type = typeof(T);

    if (!type.IsEnum)
    {
        throw new ArgumentException("T must be an enum");
    }

    return (IList<KeyValuePair<T, string>>)
            Enum.GetValues(type)
                .OfType<T>()
                .Select(e =>
                {
                    var asEnum = (Enum)Convert.ChangeType(e, typeof(Enum));
                    return new KeyValuePair<T, string>(e, asEnum.Description());
                })
                .ToArray();
}

Ahora tendrá una lista de pares clave-valor de todas las enumeraciones y su descripción. Así que simplemente asignemos esto como fuente de datos para un cuadro combinado.

var comboBox = new ComboBox();
comboBox.ValueMember = "Key"
comboBox.DisplayMember = "Value";
comboBox.DataSource = EnumUtilities.ToList<Separator>();

comboBox.SelectedIndexChanged += (sender, e) =>
{
    var selectedEnum = (Separator)comboBox.SelectedValue;
    MessageBox.Show(selectedEnum.ToString());
}

El usuario ve todas las representaciones de cadena de la enumeración y dentro de su código obtendrá el valor de enumeración deseado.


0

No podemos definir la enumeración como un tipo de cadena. Los tipos aprobados para una enumeración son byte, sbyte, short, ushort, int, uint, long o ulong.

Si necesita más detalles sobre la enumeración, siga el enlace a continuación, ese enlace lo ayudará a comprender la enumeración. Enumeración

@ narendras1414


0

Esto funciona para mi..

   public class ShapeTypes
    {
        private ShapeTypes() { }
        public static string OVAL
        {
            get
            {
                return "ov";
            }
            private set { }
        }

        public static string SQUARE
        {
            get
            {
                return "sq";
            }
            private set { }
        }

        public static string RECTANGLE
        {
            get
            {
                return "rec";
            }
            private set { }
        }
    }

0

Lo que he comenzado a hacer recientemente es usar Tuples

public static (string Fox, string Rabbit, string Horse) Animals = ("Fox", "Rabbit", "Horse");
...
public static (string Comma, string Tab, string Space) SeparatorChars = (",", "\t", " ");

-1

Clase de Enumaration

 public sealed class GenericDateTimeFormatType
    {

        public static readonly GenericDateTimeFormatType Format1 = new GenericDateTimeFormatType("dd-MM-YYYY");
        public static readonly GenericDateTimeFormatType Format2 = new GenericDateTimeFormatType("dd-MMM-YYYY");

        private GenericDateTimeFormatType(string Format)
        {
            _Value = Format;
        }

        public string _Value { get; private set; }
    }

Consumo de Enumaración

public static void Main()
{
       Country A = new Country();

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

      Console.ReadLine();
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.