Como definir um enum com valor de string?

97

Estou tentando definir Enume adicionar separadores comuns válidos que são usados ​​em arquivos CSV ou semelhantes. Em seguida, vou vinculá-lo a um ComboBoxcomo uma fonte de dados, de modo que sempre que adicionar ou remover da definição Enum, não precisarei alterar nada na caixa de combinação.

O problema é como posso definir enum com representação de string, algo como:

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

Saeid Yazdani
fonte
possível duplicata de Associating enums com strings em C #
nawfal

Respostas:

112

Você não pode - os valores enum devem ser valores integrais. Você pode usar atributos para associar um valor de string a cada valor enum ou, neste caso, se cada separador for um único caractere, você pode apenas usar o charvalor:

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

(EDITAR: Apenas para esclarecer, você não pode fazer charo tipo subjacente do enum, mas pode usar charconstantes para atribuir o valor integral correspondente a cada valor de enum. O tipo subjacente do enum acima é int.)

Em seguida, um método de extensão, se você precisar:

public string ToSeparatorString(this Separator separator)
{
    // TODO: validation
    return ((char) separator).ToString();
}
Jon Skeet
fonte
Char não é válido em enums. Msdn: "Cada tipo de enumeração tem um tipo subjacente, que pode ser qualquer tipo integral, exceto char."
agora para
8
@dowhilefor: Você pode usar um literal de char para o valor , de acordo com minha resposta. Eu testei :)
Jon Skeet
como este requisito é para arquivos, o usuário pode precisar do separador CRLF. Funcionará para esse caso também?
Maheep
Obrigado Jon, \ t conta como um char ?!
Saeid Yazdani
1
@ShaunLuttin: enums são apenas "números nomeados" - portanto, um enum de string realmente não se encaixa nesse modelo.
Jon Skeet de
82

Pelo que eu sei, você não terá permissão para atribuir valores de string a enum. O que você pode fazer é criar uma classe com constantes de string nela.

public static class SeparatorChars
{
    public static String Comma { get { return ",";} } 
    public static String Tab { get { return "\t,";} } 
    public static String Space { get { return " ";} } 
}
Maheep
fonte
9
O lado negativo dessa abordagem em oposição a outras é que você não pode enumerá-los sem fazer algo extra / especial.
caesay
Isso não ajuda a impor certos valores durante o tempo de compilação, pois separatoragora é uma string (pode ser qualquer coisa) em vez de um Separatortipo com valores válidos restritos.
ChickenFeet
71

Você pode alcançá-lo, mas exigirá um pouco de trabalho.

  1. Defina uma classe de atributo que conterá o valor da string para enum.
  2. Defina um método de extensão que retornará o valor do atributo. Por exemplo, GetStringValue (este valor Enum) retornará o valor do atributo.
  3. Então você pode definir o enum assim ..
public enum Test: int {
    [StringValue ("a")]
    Foo = 1,
    [StringValue ("b")]
    Algo = 2        
} 
  1. Para recuperar o valor de Attrinbute Test.Foo.GetStringValue ();

Consulte: Enum com valores de string em C #

Amit Rai Sharma
fonte
4
Eu sei disso, mas é obviamente único e permite que você use enums no código e no valor da string no banco de dados. Incrível
A_kat
1
Outro comentário tardio, mas esta é realmente uma solução brilhante
Alan
35

Para um enum simples de valores de string (ou qualquer outro tipo):

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

Uso: string MyValue = MyEnumClass.MyValue1;

Thierry
fonte
1
Embora não seja um enum, acho que pode ser a melhor solução para o que o usuário está tentando fazer. Às vezes, a solução mais simples é a melhor.
Zesty
28

Você não pode fazer isso com enums, mas pode fazer assim:

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

    public static string Tab = "\t";

    public static string Space = " ";
}
Fischermaen
fonte
1
+1 Embora eu ache que é a solução certa, eu mudaria o nome da classe ou mudaria o tipo para chars. Apenas para ser consistente.
agora para
Obrigado, você pode dizer o que será equivalente a comboBox.DataSource = Enum.GetValues(typeof(myEnum));neste caso?
Saeid Yazdani
1
@ Sean87: Se você quiser, eu aceitaria a resposta do JonSkeets.
Fischermaen
Acho que essa é a resposta quase certa, porque não pode ser usada dentro de switch-caseblocos. Os campos devem estar constem ordem. Mas ainda não pode ser evitado se você quiser Enum.GetValues(typeof(myEnum)).
André Santaló
7
Eu usaria em constvez de static. Constantes são somente leitura, bem como estáticas e não são atribuíveis em construtores (a menos que campos somente leitura).
Olivier Jacot-Descombes
12

Você não pode, porque enum só pode ser baseado em um tipo numérico primitivo. Você pode tentar usar um Dictionary:

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

Como alternativa, você pode usar um Dictionary<Separator, char>ou Dictionary<Separator, string>onde Separatoré um enum normal:

enum Separator
{
    Comma,
    Tab,
    Space
}

o que seria um pouco mais agradável do que manusear as cordas diretamente.

Adão
fonte
11

Uma classe que emula o comportamento de enum, mas usando em stringvez de, intpode ser criada da seguinte maneira ...

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

então...

public void PrintGrainKeyword(GrainType grain) 
{
    Console.Writeline("My Grain code is " + grain.ToString());   // Displays "My Grain code is GT_WHEAT"
}
Colmde
fonte
A única coisa que você não pode fazer GrainType myGrain = "GT_CORN", por exemplo.
colmde de
você poderia se substituísse a operadora
SSX-SL33PY
8

É meio tarde para responder, mas talvez ajude alguém no futuro. Achei mais fácil usar struct para esse tipo de problema.

O exemplo a seguir é copiar a parte colada do 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";

    }
}
taloss
fonte
Você poderia explicar por que essa abordagem é melhor do que usar uma aula?
Gerardo Grignoli
@GerardoGrignoli Não sei exatamente por que eles usam struct em vez de class no MS para esse tipo de coisa. Eu nem tentei descobrir, já que está funcionando perfeitamente para mim. Talvez tente fazer perguntas aqui na pilha ...
suchoss
5

Para as pessoas que chegam aqui em busca de uma resposta para uma pergunta mais genérica, você pode estender o conceito de classe estática se quiser que seu código se pareça com um enum.

A abordagem a seguir funciona quando você não finalizou o enum namesque deseja e enum valuesé a stringrepresentação do enam name; use nameof()para tornar sua refatoração mais simples.

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

Isso atinge a intenção de um enum que tem valores de string (como o seguinte pseudocódigo):

public enum Colours
{
    "Red",
    "Green",
    "Blue"
}
Zodman
fonte
5

Talvez seja tarde demais, mas aqui vai.

Podemos usar o atributo EnumMember para gerenciar os valores Enum.

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

Desta forma, o valor do resultado para EUnitOfMeasure será KM ou MI. Isso também pode ser visto na resposta de Andrew Whitaker .

Javier Contreras
fonte
4

Criei uma classe base para a criação de enums com valor de string no .NET. É apenas um arquivo C # que você pode copiar e colar em seus projetos ou instalar por meio do pacote NuGet denominado 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");
}

Recursos

  • Seu StringEnum se parece um pouco com um enum regular:
    // 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)
  • O Intellisense irá sugerir o nome enum se a classe for anotada com o comentário xml <completitionlist>. (Funciona em C # e VB): ie

Demonstração Intellisense

Instalação

Ou:

  • Instale o pacote StringEnum NuGet mais recente , que é baseado em .Net Standard 1.0para que seja executado em .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6, etc.
  • Ou cole a seguinte classe base StringEnum em seu projeto. ( versão mais recente )
    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.Jsonsuporte de serialização, copie esta versão estendida. StringEnum.cs

Percebi depois do fato que este código é semelhante à resposta de Ben. Sinceramente, escrevi do zero. No entanto, acho que tem alguns extras, como o <completitionlist>hack, a classe resultante se parece mais com um Enum, sem uso de reflexão em Parse (), o pacote NuGet e repo, onde espero resolver problemas e comentários recebidos.

Gerardo Grignoli
fonte
3

Com base em algumas das respostas aqui, implementei uma classe base reutilizável que imita o comportamento de um enum, mas com stringo tipo subjacente. Ele suporta várias operações, incluindo:

  1. obtendo uma lista de valores possíveis
  2. convertendo em string
  3. comparação com outros casos via .Equals, ==e!=
  4. conversão de / para JSON usando um JSON.NET JsonConverter

Esta é a classe base em sua totalidade:

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

E é assim que você implementaria seu "string enum":

[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 poderia ser usado assim:

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 alguém ache isso útil!

Ben
fonte
2

Bem, primeiro você tenta atribuir strings e não chars, mesmo que sejam apenas um caractere. use ',' em vez de ",". A próxima coisa é, enums só aceitam tipos integrais sem que charvocê possa usar o valor Unicode, mas eu recomendo fortemente que você não faça isso. Se você tiver certeza de que esses valores permanecem os mesmos, em diferentes culturas e idiomas, eu usaria uma classe estática com strings const.

por enquanto
fonte
2

Embora não seja realmente possível usar a charou a stringcomo base para um enum, acho que não é isso que você realmente gosta de fazer.

Como você mencionou, você gostaria de ter uma enumeração de possibilidades e mostrar uma representação de string disso em uma caixa de combinação. Se o usuário selecionar uma dessas representações de string, você gostaria de obter o enum correspondente. E isso é possível:

Primeiro, temos que vincular alguma string a um valor enum. Isso pode ser feito usando o método DescriptionAttributedescrito aqui ou aqui .

Agora você precisa criar uma lista de valores enum e descrições correspondentes. Isso pode ser feito usando o seguinte 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();
}

Agora você terá uma lista de pares de valores-chave de todos os enums e suas descrições. Portanto, vamos simplesmente atribuir isso como uma fonte de dados para uma caixa de combinação.

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

O usuário vê todas as representações de string do enum e dentro do seu código você obterá o valor de enum desejado.

Oliver
fonte
0

Não podemos definir enumeração como tipo de string. Os tipos aprovados para um enum são byte, sbyte, short, ushort, int, uint, long ou ulong.

Se precisar de mais detalhes sobre a enumeração, siga o link abaixo, que o ajudará a entender a enumeração. Enumeração

@ narendras1414

Narendra1414
fonte
0

Funciona para mim..

   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 { }
        }
    }
Rakesh Kr
fonte
0

O que comecei a fazer recentemente é usar tuplas

public static (string Fox, string Rabbit, string Horse) Animals = ("Fox", "Rabbit", "Horse");
...
public static (string Comma, string Tab, string Space) SeparatorChars = (",", "\t", " ");
Luke T O'Brien
fonte
-1

Aula 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 enumaração

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

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

      Console.ReadLine();
}
Mark Macneil Bikeio
fonte