Comment définir une énumération avec une valeur de chaîne?

97

J'essaie de définir Enumet d'ajouter des séparateurs communs valides qui sont utilisés dans des fichiers CSV ou similaires. Alors je vais le lier à unComboBox tant que source de données, donc chaque fois que j'ajoute ou supprime de la définition Enum, je n'aurais pas besoin de changer quoi que ce soit dans la zone de liste déroulante.

Le problème est de savoir comment définir enum avec une représentation sous forme de chaîne, quelque chose comme:

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

Saeid Yazdani
la source
duplication possible d'énumérations d' association avec des chaînes en C #
nawfal

Réponses:

113

Vous ne pouvez pas - les valeurs d'énumération doivent être des valeurs intégrales. Vous pouvez utiliser des attributs pour associer une valeur de chaîne à chaque valeur d'énumération, ou dans ce cas, si chaque séparateur est un seul caractère, vous pouvez simplement utiliser la charvaleur:

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

(EDIT: Juste pour clarifier, vous ne pouvez pas définir charle type sous-jacent de l'énumération, mais vous pouvez utiliser des charconstantes pour affecter la valeur intégrale correspondant à chaque valeur d'énumération. Le type sous-jacent de l'énumération ci-dessus estint .)

Ensuite, une méthode d'extension si vous en avez besoin:

public string ToSeparatorString(this Separator separator)
{
    // TODO: validation
    return ((char) separator).ToString();
}
Jon Skeet
la source
Char n'est pas valide dans les énumérations. Msdn: "Chaque type d'énumération a un type sous-jacent, qui peut être n'importe quel type intégral sauf char."
dowhile pour
8
@dowhilefor: Vous pouvez cependant utiliser un littéral char pour la valeur , selon ma réponse. Je l'ai testé :)
Jon Skeet
car cette exigence concerne les fichiers, l'utilisateur peut avoir besoin d'un séparateur CRLF. Cela fonctionnera-t-il aussi pour ce cas?
Maheep
Merci Jon, est-ce que \ t compte comme un personnage?!
Saeid Yazdani
1
@ShaunLuttin: les énumérations ne sont que des "nombres nommés" - donc une énumération de chaîne ne correspond vraiment pas du tout à ce modèle.
Jon Skeet
83

Autant que je sache, vous ne serez pas autorisé à attribuer des valeurs de chaîne à enum. Ce que vous pouvez faire est de créer une classe avec des constantes de chaîne.

public static class SeparatorChars
{
    public static String Comma { get { return ",";} } 
    public static String Tab { get { return "\t,";} } 
    public static String Space { get { return " ";} } 
}
Maheep
la source
9
L'inconvénient de cette approche opposée aux autres est que vous ne pouvez pas les énumérer sans faire quelque chose d'extra / spécial.
caesay
Cela n'aide pas à appliquer certaines valeurs pendant la compilation, car il separators'agit maintenant d'une chaîne (peut être n'importe quoi) au lieu d'un Separatortype avec des valeurs valides restreintes.
ChickenFeet
73

Vous pouvez y parvenir mais cela nécessitera un peu de travail.

  1. Définissez une classe d'attribut qui contiendra la valeur de chaîne pour enum.
  2. Définissez une méthode d'extension qui renverra la valeur de l'attribut. Par exemple, GetStringValue (cette valeur Enum) renverra la valeur d'attribut.
  3. Ensuite, vous pouvez définir l'énumération comme ceci.
public enum Test: int {
    [StringValue ("a")]
    Toto = 1,
    [StringValue ("b")]
    Quelque chose = 2        
} 
  1. Pour récupérer la valeur de Attrinbute Test.Foo.GetStringValue ();

Référez-vous: Enum avec des valeurs de chaîne en C #

Amit Rai Sharma
la source
5
Je connais cet ancien mais il est évidemment unique et vous permet d'utiliser des énumérations dans le code et la valeur de chaîne dans la base de données. Amazing
A_kat
1
Un autre commentaire tardif, mais c'est vraiment une solution brillante
Alan
36

Pour une simple énumération de valeurs de chaîne (ou tout autre type):

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

Usage: string MyValue = MyEnumClass.MyValue1;

Thierry
la source
1
Bien que ce ne soit pas une énumération, je pense que cela pourrait fournir la meilleure solution à ce que l'utilisateur essaie de faire. Parfois, la solution la plus simple est la meilleure.
Zesty
29

Vous ne pouvez pas faire cela avec des énumérations, mais vous pouvez le faire comme ça:

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

    public static string Tab = "\t";

    public static string Space = " ";
}
Fischermaen
la source
1
+1 Bien que je pense que c'est la bonne solution, je changerais le nom de la classe ou changerais le type en caractères. Juste pour être cohérent.
dowhile pour
Merci, pouvez-vous dire ce que sera l'équivalent comboBox.DataSource = Enum.GetValues(typeof(myEnum));dans ce cas?
Saeid Yazdani
1
@ Sean87: Je veux avoir ça, je prendrais la réponse de JonSkeets.
Fischermaen
Je pense que c'est presque la bonne réponse, car elle n'est pas utilisable à l'intérieur de switch-caseblocs. Les champs doivent être consten ordre. Mais cela ne peut toujours pas être aidé si vous le souhaitez Enum.GetValues(typeof(myEnum)).
André Santaló
7
J'utiliserais à la constplace de static. Les constantes sont en lecture seule ainsi que statiques et ne sont pas attribuables dans les constructeurs (sauf dans les champs en lecture seule).
Olivier Jacot-Descombes
12

Vous ne pouvez pas, car enum ne peut être basé que sur un type numérique primitif. Vous pouvez essayer d'utiliser un à la Dictionaryplace:

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

Alternativement, vous pouvez utiliser un Dictionary<Separator, char>ou Dictionary<Separator, string>where Separatorest une énumération normale:

enum Separator
{
    Comma,
    Tab,
    Space
}

ce qui serait un peu plus agréable que de manipuler directement les cordes.

Adam
la source
11

Une classe qui émule le comportement d'énumération mais en utilisant stringau lieu de intpeut être créée comme suit ...

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

}

Usage...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

puis...

public void PrintGrainKeyword(GrainType grain) 
{
    Console.Writeline("My Grain code is " + grain.ToString());   // Displays "My Grain code is GT_WHEAT"
}
Colmde
la source
La seule chose est que vous ne pouvez pas faire GrainType myGrain = "GT_CORN", par exemple.
colmde
vous pourriez si vous remplacez l'opérateur
SSX-SL33PY
8

Il est un peu tard pour la réponse, mais peut-être que cela aidera quelqu'un à l'avenir. J'ai trouvé plus facile d'utiliser struct pour ce genre de problème.

L'exemple suivant est une partie copiée à partir du code 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";

    }
}
teloss
la source
Pourriez-vous expliquer pourquoi cette approche est meilleure que d'utiliser une classe?
Gerardo Grignoli
@GerardoGrignoli Je ne sais pas exactement pourquoi ils utilisent struct au lieu de class dans MS pour ce genre de chose. Je n'ai même pas essayé de le savoir, car cela fonctionne parfaitement pour moi. Essayez peut-être de poser une question ici sur la pile ...
suchoss
6

Il est peut-être trop tard, mais c'est parti.

Nous pouvons utiliser l'attribut EnumMember pour gérer les valeurs Enum.

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

De cette façon, la valeur de résultat pour EUnitOfMeasure sera KM ou MI. Cela peut également être vu dans la réponse d' Andrew Whitaker .

Javier Contreras
la source
5

Pour les personnes arrivant ici à la recherche d'une réponse à une question plus générique, vous pouvez étendre le concept de classe statique si vous voulez que votre code ressemble à un fichier enum.

L'approche suivante fonctionne lorsque vous n'avez pas finalisé le que enum namesvous voulez et que enum valuessont la stringreprésentation du enam name; utilisez nameof()pour simplifier votre refactoring.

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

Cela réalise l'intention d'une énumération qui a des valeurs de chaîne (telles que le pseudocode suivant):

public enum Colours
{
    "Red",
    "Green",
    "Blue"
}
Zodman
la source
4

J'ai créé une classe de base pour créer des énumérations à valeur de chaîne dans .NET. Il s'agit simplement d'un fichier C # que vous pouvez copier et coller dans vos projets, ou installer via le package NuGet nommé StringEnum .

Usage:

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

Caractéristiques

  • Votre StringEnum ressemble un peu à une énumération régulière:
    // 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 suggérera le nom d'énumération si la classe est annotée avec le commentaire xml <completitionlist> . (Fonctionne à la fois en C # et VB): ie

Démo Intellisense

Installation

Soit:

  • Installez le dernier package NuGet StringEnum, qui est basé sur .Net Standard 1.0pour qu'il s'exécute sur .Net Core> = 1.0,.Net Framework > = 4.5, Mono> = 4.6, etc.
  • Ou collez la classe de base StringEnum suivante dans votre projet. ( dernière version )
    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;
        }
    }
  • Pour la Newtonsoft.Jsonprise en charge de la sérialisation, copiez plutôt cette version étendue. StringEnum.cs

J'ai réalisé après coup que ce code est similaire à la réponse de Ben. Je l'ai sincèrement écrit à partir de zéro. Cependant, je pense qu'il a quelques extras, comme le <completitionlist>hack, la classe résultante ressemble plus à un Enum, aucune utilisation de réflexion sur Parse (), le package NuGet et le dépôt où j'espère aborder les problèmes entrants et les commentaires.

Gerardo Grignoli
la source
3

En me basant sur certaines des réponses ici, j'ai implémenté une classe de base réutilisable qui imite le comportement d'une énumération mais avec stringcomme type sous-jacent. Il prend en charge diverses opérations, notamment:

  1. obtenir une liste de valeurs possibles
  2. conversion en chaîne
  3. comparaison avec d' autres cas par l' intermédiaire .Equals, ==et!=
  4. conversion vers / depuis JSON à l'aide d'un JsonConverter JSON.NET

Voici la classe de base dans son intégralité:

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

Et voici comment implémenter votre "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");
}

Qui pourrait être utilisé comme ceci:

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

J'espère que quelqu'un trouvera cela utile!

Ben
la source
2

Eh bien, vous essayez d'abord d'attribuer des chaînes et non des caractères, même s'il ne s'agit que d'un seul caractère. utilisez ',' au lieu de ",". La chose suivante est que les énumérations ne prennent que des types intégraux sans que charvous puissiez utiliser la valeur unicode, mais je vous conseillerais fortement de ne pas le faire. Si vous êtes certain que ces valeurs restent les mêmes, dans des cultures et des langues différentes, j'utiliserais une classe statique avec des chaînes const.

dowhilefor
la source
2

Bien qu'il ne soit vraiment pas possible d'utiliser a charou a stringcomme base pour une énumération, je pense que ce n'est pas ce que vous aimez vraiment faire.

Comme vous l'avez mentionné, vous aimeriez avoir une énumération de possibilités et afficher une représentation sous forme de chaîne de cela dans une zone de liste déroulante. Si l'utilisateur sélectionne l'une de ces représentations de chaîne, vous souhaitez obtenir l'énumération correspondante. Et c'est possible:

Nous devons d'abord lier une chaîne à une valeur d'énumération. Cela peut être fait en utilisant ce DescriptionAttributequi est décrit ici ou ici .

Vous devez maintenant créer une liste de valeurs d'énumération et des descriptions correspondantes. Cela peut être fait en utilisant la méthode suivante:

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

Vous aurez maintenant une liste des paires de valeurs clés de toutes les énumérations et leur description. Alors affectons simplement ceci comme source de données pour une zone de liste déroulante.

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

L'utilisateur voit toutes les représentations sous forme de chaîne de l'énumération et dans votre code, vous obtiendrez la valeur d'énumération souhaitée.

Oliver
la source
0

Nous ne pouvons pas définir l'énumération comme type de chaîne. Les types approuvés pour une énumération sont byte, sbyte, short, ushort, int, uint, long ou ulong.

Si vous avez besoin de plus de détails sur l'énumération, veuillez suivre le lien ci-dessous, ce lien vous aidera à comprendre l'énumération. Énumération

@ narendras1414

Narendra1414
la source
0

Ça marche pour moi..

   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
la source
0

Ce que j'ai récemment commencé à faire, c'est d'utiliser Tuples

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
la source
-1

Classe de dénombrement

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

Consommation de dénombrement

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

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

      Console.ReadLine();
}
Mark Macneil Bikeio
la source