Zeichenfolgendarstellung einer Aufzählung

912

Ich habe die folgende Aufzählung:

public enum AuthenticationMethod
{
    FORMS = 1,
    WINDOWSAUTHENTICATION = 2,
    SINGLESIGNON = 3
}

Das Problem ist jedoch, dass ich das Wort "FORMS" benötige, wenn ich nach AuthenticationMethod.FORMS und nicht nach der ID 1 frage.

Ich habe die folgende Lösung für dieses Problem gefunden ( Link ):

Zuerst muss ich ein benutzerdefiniertes Attribut namens "StringValue" erstellen:

public class StringValue : System.Attribute
{
    private readonly string _value;

    public StringValue(string value)
    {
        _value = value;
    }

    public string Value
    {
        get { return _value; }
    }

}

Dann kann ich dieses Attribut meinem Enumerator hinzufügen:

public enum AuthenticationMethod
{
    [StringValue("FORMS")]
    FORMS = 1,
    [StringValue("WINDOWS")]
    WINDOWSAUTHENTICATION = 2,
    [StringValue("SSO")]
    SINGLESIGNON = 3
}

Und natürlich brauche ich etwas, um diesen StringValue abzurufen:

public static class StringEnum
{
    public static string GetStringValue(Enum value)
    {
        string output = null;
        Type type = value.GetType();

        //Check first in our cached results...

        //Look for our 'StringValueAttribute' 

        //in the field's custom attributes

        FieldInfo fi = type.GetField(value.ToString());
        StringValue[] attrs =
           fi.GetCustomAttributes(typeof(StringValue),
                                   false) as StringValue[];
        if (attrs.Length > 0)
        {
            output = attrs[0].Value;
        }

        return output;
    }
}

Gut, jetzt habe ich die Werkzeuge, um einen String-Wert für einen Enumerator zu erhalten. Ich kann es dann so verwenden:

string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);

Okay, jetzt funktionieren all diese Dinge wie ein Zauber, aber ich finde es eine Menge Arbeit. Ich habe mich gefragt, ob es dafür eine bessere Lösung gibt.

Ich habe auch etwas mit einem Wörterbuch und statischen Eigenschaften versucht, aber das war auch nicht besser.

user29964
quelle
8
Nett! Ich kann dies verwenden, um Enum-Werte in lokalisierte Zeichenfolgen zu übersetzen.
Øyvind Skaar
5
Während Sie dies vielleicht langwierig finden, ist es tatsächlich ein ziemlich flexibler Weg, um andere Dinge zu tun. Wie einer meiner Kollegen betonte, könnte dies in vielen Fällen verwendet werden, um Enum-Helfer zu ersetzen, die Datenbankcodes Enum-Werten usw.
zuordnen
27
MSDN empfiehlt Suffix-Attributklassen mit dem Suffix "Attribut". Also "class StringValueAttribute";)
serhio
14
Ich stimme @BenAlabaster zu, dies ist eigentlich ziemlich flexibel. Sie können dies auch zu einer Erweiterungsmethode machen, indem Sie sie thisvor der Enumstatischen Methode hinzufügen . Dann können Sie tun AuthenticationMethod.Forms.GetStringValue();
Justin Pihony
5
Dieser Ansatz verwendet Reflektion zum Lesen der Attributwerte und ist sehr langsam, wenn Sie GetStringValue () meiner Erfahrung nach mehrmals aufrufen müssen. Das Typ-Safe-Enum-Muster ist schneller.
Rn222

Antworten:

868

Versuchen Sie es mit einem Typ-Safe-Enum- Muster.

public sealed class AuthenticationMethod {

    private readonly String name;
    private readonly int value;

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (3, "SSN");        

    private AuthenticationMethod(int value, String name){
        this.name = name;
        this.value = value;
    }

    public override String ToString(){
        return name;
    }

}

Aktualisieren Die explizite (oder implizite) Typkonvertierung kann von durchgeführt werden

  • Hinzufügen eines statischen Feldes mit Zuordnung

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • nb Damit die Initialisierung der Felder "enum member" beim Aufrufen des Instanzkonstruktors keine NullReferenceException auslöst, müssen Sie das Feld Dictionary vor den Feldern "enum member" in Ihrer Klasse platzieren. Dies liegt daran, dass statische Feldinitialisierer in der Deklarationsreihenfolge und vor dem statischen Konstruktor aufgerufen werden, wodurch die seltsame und notwendige, aber verwirrende Situation entsteht, dass der Instanzkonstruktor aufgerufen werden kann, bevor alle statischen Felder initialisiert wurden und bevor der statische Konstruktor aufgerufen wird.
  • Füllen Sie diese Zuordnung im Instanzkonstruktor

    instance[name] = this;
  • und Hinzufügen eines benutzerdefinierten Typkonvertierungsoperators

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }
Jakub Šturc
quelle
17
Es sieht aus wie eine Aufzählung, ist aber keine Aufzählung. Ich kann mir vorstellen, dass dies einige interessante Probleme verursacht, wenn Leute versuchen, AuthenticationMethods zu vergleichen. Sie müssen wahrscheinlich auch verschiedene Gleichheitsoperatoren überladen.
Ameise
36
@Ant: Ich muss nicht. Da wir nur eine Instanz jeder AuthenticationMethod haben, funktioniert die von Object geerbte Referenzgleichheit einwandfrei.
Jakub Šturc
10
@tyriker: Compiler tut. Der Konstruktor ist privat, sodass Sie keine neue Instanz erstellen können. Auch auf statische Mitglieder kann nicht über eine Instanz zugegriffen werden.
Jakub Šturc
21
@ Jakub Sehr interessant. Ich musste damit spielen, um herauszufinden, wie man es benutzt und seine Vorteile erkennt. Es ist eine öffentliche, nicht statische Klasse, kann jedoch nicht instanziiert werden und Sie können nur auf ihre statischen Mitglieder zugreifen. Im Grunde verhält es sich wie eine Aufzählung. Aber das Beste daran ist, dass die statischen Elemente von der Klasse eingegeben werden und keine generische Zeichenfolge oder int. Es ist eine ... [warte darauf] ... sichere Aufzählung! Danke, dass du mir geholfen hast zu verstehen.
Tyriker
6
@ kiran Ich habe unten eine leicht modifizierte Version von Jakub Šturcs Antwort gepostet, die es ermöglicht, sie mit Switch-Case-Anweisungen zu verwenden.
Daher
228

Verwenden Sie die Methode

Enum.GetName(Type MyEnumType,  object enumvariable)  

wie in (Angenommen, es Shipperhandelt sich um eine definierte Aufzählung)

Shipper x = Shipper.FederalExpress;
string s = Enum.GetName(typeof(Shipper), x);

Es gibt eine Reihe anderer statischer Methoden in der Enum-Klasse, die ebenfalls untersucht werden sollten ...

Charles Bretana
quelle
5
Genau. Ich habe ein benutzerdefiniertes Attribut für eine Zeichenfolgenbeschreibung erstellt, aber das liegt daran, dass ich eine benutzerfreundliche Version (mit Leerzeichen und anderen Sonderzeichen) möchte, die leicht an eine ComboBox oder ähnliches gebunden werden kann.
lc.
5
Enum.GetName spiegelt die Feldnamen in der Aufzählung wider - genau wie .ToString (). Wenn die Leistung ein Problem darstellt, kann dies ein Problem sein. Ich würde mir darüber keine Sorgen machen, wenn Sie nicht viele Aufzählungen konvertieren.
Keith
8
Eine weitere Option, die Sie in Betracht ziehen sollten, wenn Sie eine Aufzählung mit zusätzlicher Funktionalität benötigen, ist das "Rollen Ihrer eigenen" mithilfe einer Struktur. Sie fügen statische schreibgeschützte Eigenschaften hinzu, um die Aufzählungswerte darzustellen, die für Konstruktoren initialisiert werden, die einzelne Instanzen der Struktur generieren ...
Charles Bretana
1
Dann können Sie beliebige andere Strukturelemente hinzufügen, um die Funktionen zu implementieren, die diese "Aufzählung" haben soll ...
Charles Bretana
2
Das Problem hierbei ist, dass GetName nicht lokalisierbar ist. Das ist nicht immer ein Problem, aber es ist etwas, das man beachten muss.
Joel Coehoorn
79

Sie können mit ToString () auf den Namen und nicht auf den Wert verweisen.

Console.WriteLine("Auth method: {0}", AuthenticationMethod.Forms.ToString());

Die Dokumentation finden Sie hier:

http://msdn.microsoft.com/en-us/library/16c1xs4z.aspx

... und wenn Sie Ihre Aufzählungen in Pascal Case benennen (wie ich - wie ThisIsMyEnumValue = 1 usw.), können Sie das freundliche Formular mit einer sehr einfachen Regex drucken:

static string ToFriendlyCase(this string EnumString)
{
    return Regex.Replace(EnumString, "(?!^)([A-Z])", " $1");
}

die leicht von jedem String aufgerufen werden kann:

Console.WriteLine("ConvertMyCrazyPascalCaseSentenceToFriendlyCase".ToFriendlyCase());

Ausgänge:

Wandle meinen verrückten Pascal-Fall in einen freundlichen Fall um

Das erspart es, durch die Häuser zu laufen, benutzerdefinierte Attribute zu erstellen und diese an Ihre Aufzählungen anzuhängen oder Nachschlagetabellen zu verwenden, um einen Aufzählungswert mit einer benutzerfreundlichen Zeichenfolge zu verbinden. Das Beste ist, dass er sich selbst verwaltet und für jede Pascal Case-Zeichenfolge verwendet werden kann, die unendlich ist wiederverwendbarer. Natürlich erlaubt es dir nicht, einen anderen zu haben Anzeigenamen als Ihre Aufzählung haben, die Ihre Lösung bietet.

Ich mag Ihre ursprüngliche Lösung für komplexere Szenarien. Sie könnten Ihre Lösung noch einen Schritt weiter gehen und GetStringValue zu einer Erweiterungsmethode Ihrer Aufzählung machen, und dann müssten Sie nicht wie StringEnum.GetStringValue darauf verweisen ...

public static string GetStringValue(this AuthenticationMethod value)
{
  string output = null;
  Type type = value.GetType();
  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs = fi.GetCustomAttributes(typeof(StringValue), false) as StringValue[];
  if (attrs.Length > 0)
    output = attrs[0].Value;
  return output;
}

Sie können dann einfach direkt von Ihrer Enum-Instanz darauf zugreifen:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
BenAlabaster
quelle
2
Dies hilft nicht, wenn der "freundliche Name" ein Leerzeichen benötigt. Wie "Forms Authentication"
Ray Booysen
4
Stellen Sie daher sicher, dass die Aufzählung mit Großbuchstaben wie FormsAuthentication benannt ist, und fügen Sie ein Leerzeichen vor Großbuchstaben ein, die nicht am Anfang stehen. Es ist keine
Hexerei
4
Der automatische Abstand von Pascal-Fallnamen wird problematisch, wenn sie Abkürzungen enthalten, die großgeschrieben werden sollen, z. B. XML oder GPS.
Richard Ev
2
@RichardEv, dafür gibt es keinen perfekten regulären Ausdruck, aber hier ist einer, der mit Abkürzungen etwas besser funktionieren sollte. "(?!^)([^A-Z])([A-Z])", "$1 $2". So HereIsATESTwird Here Is ATEST.
Ersatzbytes
Nicht elegant, diese kleinen "Hacks" zu machen, was sie sind. Ich verstehe, was das OP sagt, und ich versuche, eine ähnliche Lösung zu finden, dh die Eleganz von Enums zu nutzen, aber leicht auf die zugehörige Nachricht zugreifen zu können. Die einzige Lösung, die ich mir vorstellen kann, besteht darin, eine Art Zuordnung zwischen dem Namen der Aufzählung und einem Zeichenfolgenwert anzuwenden, aber das Problem der Verwaltung der Zeichenfolgendaten wird dadurch nicht gelöst (dies ist jedoch praktisch für Szenarien, in denen mehrere Regionen usw. Erforderlich sind )
Tahir Khalid
72

Leider ist die Reflexion, um Attribute für Aufzählungen zu erhalten, ziemlich langsam:

Siehe diese Frage: Kennt jemand einen schnellen Weg, um zu benutzerdefinierten Attributen für einen Aufzählungswert zu gelangen?

Das .ToString()ist auch bei Aufzählungen ziemlich langsam.

Sie können jedoch Erweiterungsmethoden für Aufzählungen schreiben:

public static string GetName( this MyEnum input ) {
    switch ( input ) {
        case MyEnum.WINDOWSAUTHENTICATION:
            return "Windows";
        //and so on
    }
}

Dies ist nicht großartig, geht aber schnell und erfordert keine Reflexion für Attribute oder Feldnamen.


C # 6 Update

Wenn Sie C # 6 dann die neuen verwenden können nameofBediener arbeiten für Aufzählungen, so nameof(MyEnum.WINDOWSAUTHENTICATION)umgewandelt werden , um "WINDOWSAUTHENTICATION"bei der Kompilierung , es ist der schnellste Weg zu machen Enum - Namen zu erhalten.

Beachten Sie, dass dadurch die explizite Aufzählung in eine Inline-Konstante konvertiert wird, sodass dies für Aufzählungen in einer Variablen nicht funktioniert. Damit:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Aber...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"
Keith
quelle
24
Sie können die Attributwerte einmal abrufen und in ein Wörterbuch <MyEnum, string> einfügen, um den deklarativen Aspekt beizubehalten.
Jon Skeet
1
Ja, das haben wir in einer App mit vielen Aufzählungen gemacht, als wir herausfanden, dass das Spiegelbild der Flaschenhals war.
Keith
Vielen Dank, Jon und Keith. Am Ende habe ich Ihren Wörterbuchvorschlag verwendet. Funktioniert super (und schnell!).
Helge Klein
@ JonSkeet Ich weiß, das ist alt. Aber wie würde man das erreichen?
user919426
2
@ user919426: Willst du erreichen? Einfügen in ein Wörterbuch? Erstellen Sie einfach ein Wörterbuch, idealerweise mit einem Sammlungsinitialisierer. Es ist nicht klar, wonach Sie fragen.
Jon Skeet
59

Ich benutze eine Erweiterungsmethode:

public static class AttributesHelperExtension
    {
        public static string ToDescription(this Enum value)
        {
            var da = (DescriptionAttribute[])(value.GetType().GetField(value.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return da.Length > 0 ? da[0].Description : value.ToString();
        }
}

Jetzt dekorieren Sie die enum mit:

public enum AuthenticationMethod
{
    [Description("FORMS")]
    FORMS = 1,
    [Description("WINDOWSAUTHENTICATION")]
    WINDOWSAUTHENTICATION = 2,
    [Description("SINGLESIGNON ")]
    SINGLESIGNON = 3
}

Wenn du anrufst

AuthenticationMethod.FORMS.ToDescription()du wirst bekommen "FORMS".

Mangesh Pimpalkar
quelle
1
Ich musste hinzufügen using System.ComponentModel;Außerdem funktioniert diese Methode nur, wenn der String-Wert mit dem Namen der Aufzählung übereinstimmen soll. OP wollte einen anderen Wert.
Elcool
2
Meinst du nicht, wenn du anrufst AuthenticationMethod.FORMS.ToDescription()?
nicodemus13
41

Verwenden Sie einfach die ToString()Methode

public enum any{Tomato=0,Melon,Watermelon}

Um auf die Zeichenfolge zu verweisen Tomato, verwenden Sie einfach

any.Tomato.ToString();
chepe
quelle
Beeindruckend. Das war einfach. Ich weiß, dass das OP benutzerdefinierte Zeichenfolgenbeschreibungen hinzufügen wollte, aber das war es, was ich brauchte. Ich hätte es im Nachhinein wissen müssen, aber ich bin die Enum.GetName-Route gegangen.
Rafe
7
Warum erschweren alle anderen dies zu sehr?
Brent
18
@Brent Weil Sie meistens einen anderen .ToString()Wert haben als den benutzerfreundlichen Wert, den Sie benötigen.
Novitchi S
2
@Brent - weil dies anders ist als die gestellte Frage. Die Frage ist, wie Sie diese Zeichenfolge aus einer Variablen erhalten, der ein Aufzählungswert zugewiesen wurde. Das ist zur Laufzeit dynamisch. Hiermit wird die Definition des Typs überprüft und zur Laufzeit festgelegt.
Hogan
1
@Hogan - das ToString () funktioniert auch mit Variablen: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV
29

Sehr einfache Lösung hierfür mit .Net 4.0 und höher. Es wird kein anderer Code benötigt.

public enum MyStatus
{
    Active = 1,
    Archived = 2
}

Um den String zu erhalten, verwenden Sie einfach:

MyStatus.Active.ToString("f");

oder

MyStatus.Archived.ToString("f");`

Der Wert ist "Aktiv" oder "Archiviert".

Informationen zu den verschiedenen Zeichenfolgenformaten (das "f" von oben) beim Aufrufen finden Enum.ToStringSie auf dieser Seite " Zeichenfolgen im Aufzählungsformat"

David C.
quelle
28

Ich verwende das Description-Attribut aus dem System.ComponentModel-Namespace. Dekorieren Sie einfach die Aufzählung und rufen Sie sie mit diesem Code ab:

public static string GetDescription<T>(this object enumerationValue)
            where T : struct
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");
            }

            //Tries to find a DescriptionAttribute for a potential friendly name
            //for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                {
                    //Pull out the description value
                    return ((DescriptionAttribute)attrs[0]).Description;
                }
            }
            //If we have no description attribute, just return the ToString of the enum
            return enumerationValue.ToString();

        }

Als Beispiel:

public enum Cycle : int
{        
   [Description("Daily Cycle")]
   Daily = 1,
   Weekly,
   Monthly
}

Dieser Code eignet sich gut für Aufzählungen, bei denen Sie keinen "Anzeigenamen" benötigen, und gibt nur den .ToString () der Aufzählung zurück.

Ray Booysen
quelle
27

Ich mag die Antwort von Jakub Šturc sehr, aber es ist ein Nachteil, dass Sie sie nicht mit einer Switch-Case-Anweisung verwenden können. Hier ist eine leicht modifizierte Version seiner Antwort, die mit einer switch-Anweisung verwendet werden kann:

public sealed class AuthenticationMethod
{
    #region This code never needs to change.
    private readonly string _name;
    public readonly Values Value;

    private AuthenticationMethod(Values value, String name){
        this._name = name;
        this.Value = value;
    }

    public override String ToString(){
        return _name;
    }
    #endregion

    public enum Values
    {
        Forms = 1,
        Windows = 2,
        SSN = 3
    }

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (Values.Forms, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (Values.Windows, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (Values.SSN, "SSN");
}

Sie erhalten also alle Vorteile der Antwort von Jakub Šturc, und wir können sie mit einer switch-Anweisung wie der folgenden verwenden:

var authenticationMethodVariable = AuthenticationMethod.FORMS;  // Set the "enum" value we want to use.
var methodName = authenticationMethodVariable.ToString();       // Get the user-friendly "name" of the "enum" value.

// Perform logic based on which "enum" value was chosen.
switch (authenticationMethodVariable.Value)
{
    case authenticationMethodVariable.Values.Forms: // Do something
        break;
    case authenticationMethodVariable.Values.Windows: // Do something
        break;
    case authenticationMethodVariable.Values.SSN: // Do something
        break;      
}
tödlicher Hund
quelle
Eine kürzere Lösung wäre, die Aufzählungen {} zu entfernen und stattdessen statisch zu zählen, wie viele Aufzählungen Sie erstellt haben. Dies bietet auch den Vorteil, dass Sie der Aufzählungsliste keine neue Instanz hinzufügen müssen. zB public static int nextAvailable { get; private set; }dann im Konstruktorthis.Value = nextAvailable++;
kjhf
Interessante Idee @kjhf. Ich befürchte jedoch, dass sich auch der den Enum-Werten zugewiesene Wert ändern könnte, wenn jemand den Code neu anordnet. Dies könnte beispielsweise dazu führen, dass der falsche Aufzählungswert abgerufen wird, wenn der Aufzählungswert in einer Datei / Datenbank gespeichert wird, die Reihenfolge der Zeilen "new AuthenticationMethod (...)" geändert wird (z. B. wird eine entfernt) und dann Ausführen der App und Abrufen des Aufzählungswerts aus der Datei / Datenbank; Der Aufzählungswert stimmt möglicherweise nicht mit der ursprünglich gespeicherten Authentifizierungsmethode überein.
tödlicher Hund
Guter Punkt - obwohl ich hoffe, dass sich die Leute in diesen speziellen Fällen nicht auf den ganzzahligen Wert der Aufzählung verlassen (oder den Aufzählungscode neu ordnen). Dieser Wert wird lediglich als Schalter und möglicherweise als Alternative zu .Equals () und verwendet. GetHashCode (). Wenn Sie besorgt sind, können Sie jederzeit einen großen Kommentar mit "NICHT REORDER" abgeben: p
kjhf
Können Sie den =Bediener nicht einfach überlasten , damit der Schalter funktioniert? Ich habe dies in VB getan und kann es jetzt in select caseAnweisung verwenden.
user1318499
@ user1318499 Nein, C # hat strengere Regeln für die switch-Anweisung als VB. Sie können keine Klasseninstanzen für die Case-Anweisung verwenden. Sie können nur konstante Grundelemente verwenden.
tödlicher Hund
13

Ich verwende eine Kombination aus mehreren der oben genannten Vorschläge, kombiniert mit etwas Caching. Jetzt kam mir die Idee von einem Code, den ich irgendwo im Internet gefunden habe, aber ich kann mich weder erinnern, woher ich ihn habe, noch finde ich ihn. Wenn also jemand etwas findet, das ähnlich aussieht, kommentieren Sie dies bitte mit der Zuschreibung.

Wie auch immer, die Verwendung betrifft die Typkonverter. Wenn Sie also an die Benutzeroberfläche binden, funktioniert dies einfach. Sie können das Jakub-Muster für eine schnelle Code-Suche erweitern, indem Sie vom Typkonverter in die statischen Methoden initialisieren.

Die Basisverwendung würde so aussehen

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
    // The custom type converter will use the description attribute
    [Description("A custom description")]
    ValueWithCustomDescription,

   // This will be exposed exactly.
   Exact
}

Der Code für den benutzerdefinierten Enum-Typ-Konverter lautet wie folgt:

public class CustomEnumTypeConverter<T> : EnumConverter
    where T : struct
{
    private static readonly Dictionary<T,string> s_toString = 
      new Dictionary<T, string>();

    private static readonly Dictionary<string, T> s_toValue = 
      new Dictionary<string, T>();

    private static bool s_isInitialized;

    static CustomEnumTypeConverter()
    {
        System.Diagnostics.Debug.Assert(typeof(T).IsEnum,
          "The custom enum class must be used with an enum type.");
    }

    public CustomEnumTypeConverter() : base(typeof(T))
    {
        if (!s_isInitialized)
        {
            Initialize();
            s_isInitialized = true;
        }
    }

    protected void Initialize()
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            string description = GetDescription(item);
            s_toString[item] = description;
            s_toValue[description] = item;
        }
    }

    private static string GetDescription(T optionValue)
    {
        var optionDescription = optionValue.ToString();
        var optionInfo = typeof(T).GetField(optionDescription);
        if (Attribute.IsDefined(optionInfo, typeof(DescriptionAttribute)))
        {
            var attribute = 
              (DescriptionAttribute)Attribute.
                 GetCustomAttribute(optionInfo, typeof(DescriptionAttribute));
            return attribute.Description;
        }
        return optionDescription;
    }

    public override object ConvertTo(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, 
       object value, Type destinationType)
    {
        var optionValue = (T)value;

        if (destinationType == typeof(string) && 
            s_toString.ContainsKey(optionValue))
        {
            return s_toString[optionValue];
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, object value)
    {
        var stringValue = value as string;

        if (!string.IsNullOrEmpty(stringValue) && s_toValue.ContainsKey(stringValue))
        {
            return s_toValue[stringValue];
        }

        return base.ConvertFrom(context, culture, value);
    }
}

}}

Steve Mitcham
quelle
12

In Ihrer Frage haben Sie nie gesagt, dass Sie den numerischen Wert der Aufzählung tatsächlich irgendwo benötigen.

Wenn Sie keine Aufzählung vom Typ string benötigen (die kein integraler Typ ist und daher keine Basis für die Aufzählung sein kann), haben Sie folgende Möglichkeiten:

    static class AuthenticationMethod
    {
        public static readonly string
            FORMS = "Forms",
            WINDOWSAUTHENTICATION = "WindowsAuthentication";
    }

Sie können dieselbe Syntax wie enum verwenden, um darauf zu verweisen

if (bla == AuthenticationMethod.FORMS)

Es ist etwas langsamer als bei numerischen Werten (Vergleichen von Zeichenfolgen anstelle von Zahlen), aber auf der positiven Seite wird keine Reflexion (langsam) verwendet, um auf die Zeichenfolge zuzugreifen.

ILIA BROUDNO
quelle
Wenn Sie "const" anstelle von "static readonly" verwenden, können Sie die Werte als Fallbezeichnungen in einer switch-Anweisung verwenden.
Ed N.
11

Wie ich das als Erweiterungsmethode gelöst habe:

using System.ComponentModel;
public static string GetDescription(this Enum value)
{
    var descriptionAttribute = (DescriptionAttribute)value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(false)
        .Where(a => a is DescriptionAttribute)
        .FirstOrDefault();

    return descriptionAttribute != null ? descriptionAttribute.Description : value.ToString();
}

Aufzählung:

public enum OrderType
{
    None = 0,
    [Description("New Card")]
    NewCard = 1,
    [Description("Reload")]
    Refill = 2
}

Verwendung (wobei o.OrderType eine Eigenschaft mit demselben Namen wie die Aufzählung ist):

o.OrderType.GetDescription()

Das gibt mir eine Zeichenfolge von "New Card" oder "Reload" anstelle des tatsächlichen Aufzählungswerts NewCard and Refill.

Sec
quelle
Der Vollständigkeit halber sollten Sie eine Kopie Ihrer DescriptionAttribute-Klasse beifügen.
Bernie White
3
Bernie, DescriptionAttribute ist in System.ComponentModel
agentnega
11

Aktualisieren: Besuch dieser Seite, 8 Jahre später, nachdem ich C # lange nicht berührt habe, scheint meine Antwort nicht mehr die beste Lösung zu sein. Ich mag die Konverterlösung, die mit Attributfunktionen verbunden ist, sehr.

Wenn Sie dies lesen, stellen Sie bitte sicher, dass Sie auch andere Antworten lesen.
(Hinweis: Sie sind über diesem)


Wie die meisten von Ihnen hat mir die ausgewählte Antwort von Jakub Šturc sehr gut gefallen , aber ich hasse es auch, Code zu kopieren und und zu versuchen, es so wenig wie möglich zu tun.

Deshalb habe ich mich für eine EnumBase-Klasse entschieden, von der der größte Teil der Funktionalität geerbt / integriert ist, sodass ich mich auf den Inhalt anstatt auf das Verhalten konzentrieren kann.

Das Hauptproblem bei diesem Ansatz basiert auf der Tatsache, dass Enum-Werte zwar typsichere Instanzen sind, die Interaktion jedoch mit der statischen Implementierung des Enum-Klassentyps erfolgt. Mit ein wenig Hilfe von Generika-Magie habe ich endlich die richtige Mischung gefunden. Hoffe, jemand findet das genauso nützlich wie ich.

Ich werde mit Jakubs Beispiel beginnen, aber Vererbung und Generika verwenden:

public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
{
    public static readonly AuthenticationMethod FORMS =
        new AuthenticationMethod(1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION =
        new AuthenticationMethod(2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON =
        new AuthenticationMethod(3, "SSN");

    private AuthenticationMethod(int Value, String Name)
        : base( Value, Name ) { }
    public new static IEnumerable<AuthenticationMethod> All
    { get { return EnumBase<AuthenticationMethod, int>.All; } }
    public static explicit operator AuthenticationMethod(string str)
    { return Parse(str); }
}

Und hier ist die Basisklasse:

using System;
using System.Collections.Generic;
using System.Linq; // for the .AsEnumerable() method call

// E is the derived type-safe-enum class
// - this allows all static members to be truly unique to the specific
//   derived class
public class EnumBase<E, T> where E: EnumBase<E, T>
{
    #region Instance code
    public T Value { get; private set; }
    public string Name { get; private set; }

    protected EnumBase(T EnumValue, string Name)
    {
        Value = EnumValue;
        this.Name = Name;
        mapping.Add(Name, this);
    }

    public override string ToString() { return Name; }
    #endregion

    #region Static tools
    static private readonly Dictionary<string, EnumBase<E, T>> mapping;
    static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
    protected static E Parse(string name)
    {
        EnumBase<E, T> result;
        if (mapping.TryGetValue(name, out result))
        {
            return (E)result;
        }

        throw new InvalidCastException();
    }
    // This is protected to force the child class to expose it's own static
    // method.
    // By recreating this static method at the derived class, static
    // initialization will be explicit, promising the mapping dictionary
    // will never be empty when this method is called.
    protected static IEnumerable<E> All
    { get { return mapping.Values.AsEnumerable().Cast<E>(); } }
    #endregion
}
Schlosser
quelle
Möglicherweise können Sie den abgeleiteten statischen Konstruktor vom statischen Basiskonstruktor aufrufen. Ich prüfe
Cory-G
10

Ich stimme Keith zu, kann aber (noch) nicht abstimmen.

Ich verwende eine statische Methode und eine swith-Anweisung, um genau das zurückzugeben, was ich will. In der Datenbank speichere ich tinyint und mein Code verwendet nur die tatsächliche Aufzählung, sodass die Zeichenfolgen den Anforderungen der Benutzeroberfläche entsprechen. Nach zahlreichen Tests ergab dies die beste Leistung und die größte Kontrolle über die Ausgabe.

public static string ToSimpleString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "ComplexForms";
             break;
     }
}

public static string ToFormattedString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "Complex Forms";
             break;
     }
}

Nach einigen Angaben führt dies jedoch zu einem möglichen Wartungsalptraum und einem gewissen Codegeruch. Ich versuche, ein Auge auf lange und viele Aufzählungen zu haben oder auf solche, die sich häufig ändern. Ansonsten war dies eine großartige Lösung für mich.

Tony Basallo
quelle
10

Wenn Sie hierher gekommen sind, um eine einfache "Aufzählung" zu implementieren, deren Werte jedoch Zeichenfolgen anstelle von Ints sind, ist hier die einfachste Lösung:

    public sealed class MetricValueList
    {
        public static readonly string Brand = "A4082457-D467-E111-98DC-0026B9010912";
        public static readonly string Name = "B5B5E167-D467-E111-98DC-0026B9010912";
    }

Implementierung:

var someStringVariable = MetricValueList.Brand;
Grinn
quelle
2
Es ist wahrscheinlich besser, die Variablen consts zu erstellen, als sie zu verwenden static readonly.
AndyGeek
1
consts eignen sich nicht für öffentlich zugängliche Klassen, da sie in der Kompilierungszeit gebacken werden. Sie können eine DLL eines Drittanbieters nicht ersetzen, ohne den gesamten Code mit consts neu zu kompilieren. Der Leistungsoffset von consts gegenüber statischem schreibgeschütztem Wert ist vernachlässigbar.
Kristian Williams
7

Wenn ich mit diesem Problem konfrontiert werde, gibt es einige Fragen, auf die ich zuerst Antworten zu finden versuche:

  • Sind die Namen meiner Enum-Werte für diesen Zweck ausreichend freundlich oder muss ich freundlichere angeben?
  • Muss ich eine Hin- und Rückfahrt machen? Das heißt, muss ich Textwerte nehmen und sie in Aufzählungswerte analysieren?
  • Muss ich das für viele Aufzählungen in meinem Projekt tun oder nur für eine?
  • In welchen UI-Elementen werde ich diese Informationen präsentieren - insbesondere bin ich an die UI gebunden oder verwende Eigenschaftenblätter?
  • Muss dies lokalisierbar sein?

Der einfachste Weg, dies zu tun, ist mit Enum.GetValue(und unterstützt das Roundtripping mit Enum.Parse). Es lohnt sich auch oft, eine zu TypeConvertererstellen, wie Steve Mitcham vorschlägt, um die UI-Bindung zu unterstützen. (Es ist nicht notwendig, eine zu bauenTypeConverter wenn Sie Eigenschaftenblätter verwenden, was eines der schönen Dinge an Eigenschaftenblättern ist. Obwohl Lord weiß, dass sie ihre eigenen Probleme haben.)

Wenn die Antworten auf die obigen Fragen darauf hindeuten, dass dies nicht funktionieren wird, besteht mein nächster Schritt im Allgemeinen darin, eine statische Dictionary<MyEnum, string>oder möglicherweise eine statische zu erstellen und zu füllen Dictionary<Type, Dictionary<int, string>>. Ich neige dazu, den Zwischenschritt zum Dekorieren des Codes mit Attributen zu überspringen, da normalerweise als nächstes die Notwendigkeit besteht, die benutzerfreundlichen Werte nach der Bereitstellung zu ändern (häufig, aber nicht immer, aufgrund der Lokalisierung).

Robert Rossney
quelle
7

Ich wollte dies als Kommentar zu dem unten zitierten Beitrag posten, konnte es aber nicht, weil ich nicht genug Repräsentanten habe - bitte stimmen Sie nicht ab. Der Code enthielt einen Fehler, und ich wollte Personen, die versuchen, diese Lösung zu verwenden, darauf hinweisen:

[TypeConverter(typeof(CustomEnumTypeConverter(typeof(MyEnum))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,
  // This will be exposed exactly.
  Exact
}

sollte sein

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,

  // This will be exposed exactly.
  Exact
}

Genial!

Paula Bean
quelle
5

Meine Variante

public struct Colors
{
    private String current;

    private static string red = "#ff0000";
    private static string green = "#00ff00";
    private static string blue = "#0000ff";

    private static IList<String> possibleColors; 

    public static Colors Red { get { return (Colors) red; } }
    public static Colors Green { get { return (Colors) green; } }
    public static Colors Blue { get { return (Colors) blue; } }

    static Colors()
    {
        possibleColors = new List<string>() {red, green, blue};
    }

    public static explicit operator String(Colors value)
    {
        return value.current;
    }

    public static explicit operator Colors(String value)
    {
        if (!possibleColors.Contains(value))
        {
            throw new InvalidCastException();
        }

        Colors color = new Colors();
        color.current = value;
        return color;
    }

    public static bool operator ==(Colors left, Colors right)
    {
        return left.current == right.current;
    }

    public static bool operator !=(Colors left, Colors right)
    {
        return left.current != right.current;
    }

    public bool Equals(Colors other)
    {
        return Equals(other.current, current);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != typeof(Colors)) return false;
        return Equals((Colors)obj);
    }

    public override int GetHashCode()
    {
        return (current != null ? current.GetHashCode() : 0);
    }

    public override string ToString()
    {
        return current;
    }
}

Code sieht ein bisschen hässlich aus, aber die Verwendung dieser Struktur ist ziemlich präsentativ.

Colors color1 = Colors.Red;
Console.WriteLine(color1); // #ff0000

Colors color2 = (Colors) "#00ff00";
Console.WriteLine(color2); // #00ff00

// Colors color3 = "#0000ff"; // Compilation error
// String color4 = Colors.Red; // Compilation error

Colors color5 = (Colors)"#ff0000";
Console.WriteLine(color1 == color5); // True

Colors color6 = (Colors)"#00ff00";
Console.WriteLine(color1 == color6); // False

Ich denke auch, wenn viele solcher Aufzählungen erforderlich sind, könnte die Codegenerierung (z. B. T4) verwendet werden.

Razoomnick
quelle
4

Option 1:

public sealed class FormsAuth
{
     public override string ToString{return "Forms Authtentication";}
}
public sealed class WindowsAuth
{
     public override string ToString{return "Windows Authtentication";}
}

public sealed class SsoAuth
{
     public override string ToString{return "SSO";}
}

und dann

object auth = new SsoAuth(); //or whatever

//...
//...
// blablabla

DoSomethingWithTheAuth(auth.ToString());

Option 2:

public enum AuthenticationMethod
{
        FORMS = 1,
        WINDOWSAUTHENTICATION = 2,
        SINGLESIGNON = 3
}

public class MyClass
{
    private Dictionary<AuthenticationMethod, String> map = new Dictionary<AuthenticationMethod, String>();
    public MyClass()
    {
         map.Add(AuthenticationMethod.FORMS,"Forms Authentication");
         map.Add(AuthenticationMethod.WINDOWSAUTHENTICATION ,"Windows Authentication");
         map.Add(AuthenticationMethod.SINGLESIGNON ,"SSo Authentication");
    }
}
Pablo Retyk
quelle
4

Wenn Sie über das Problem nachdenken, das wir zu lösen versuchen, ist es keine Aufzählung, die wir überhaupt brauchen. Wir brauchen ein Objekt, mit dem eine bestimmte Anzahl von Werten miteinander verknüpft werden kann. mit anderen Worten, um eine Klasse zu definieren.

Das typsichere Aufzählungsmuster von Jakub Šturc ist die beste Option, die ich hier sehe.

Schau es dir an:

  • Es hat einen privaten Konstruktor, sodass nur die Klasse selbst die zulässigen Werte definieren kann.
  • Da es sich um eine versiegelte Klasse handelt, können Werte nicht durch Vererbung geändert werden.
  • Es ist typsicher, sodass Ihre Methoden nur diesen Typ benötigen.
  • Beim Zugriff auf die Werte tritt keine Beeinträchtigung der Reflexionsleistung auf.
  • Zuletzt kann es geändert werden, um mehr als zwei Felder miteinander zu verknüpfen, z. B. einen Namen, eine Beschreibung und einen numerischen Wert.
Harvo
quelle
4

Für mich ist der pragmatische Ansatz Klasse für Klasse, Beispiel:

public class MSEModel
{
    class WITS
    {
        public const string DATE = "5005";
        public const string TIME = "5006";
        public const string MD = "5008";
        public const string ROP = "5075";
        public const string WOB = "5073";
        public const string RPM = "7001";
... 
    }
Harveyt
quelle
4

Ich habe eine Basisklasse zum Erstellen von Aufzählungen mit Zeichenfolgen in .NET erstellt. Es ist nur eine C # -Datei, die Sie kopieren und in Ihre Projekte einfügen oder über das NuGet-Paket StringEnum installieren können . GitHub Repo

  • Intellisense schlägt den Namen der Aufzählung vor, wenn die Klasse mit dem XML-Kommentar versehen ist <completitionlist>. (Funktioniert sowohl in C # als auch in VB)

Intellisense-Demo

  • Verwendung ähnlich einer regulären Aufzählung:
///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = Create("#FF0000");
    public static readonly HexColor Green = Create("#00FF00");
    public static readonly HexColor Red = Create("#000FF");
}
    // 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)

Installation:

  • Fügen Sie die folgende StringEnum-Basisklasse in Ihr Projekt ein. ( neueste Version )
  • Oder installieren Sie das StringEnum NuGet-Paket, das darauf basiert, .Net Standard 1.0dass es auf .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6 usw. ausgeführt wird.
    /// <summary>
    /// Base class for creating string-valued enums in .NET.<br/>
    /// Provides static Parse() and TryParse() methods and implicit cast to string.
    /// </summary>
    /// <example> 
    /// <code>
    /// class Color : StringEnum &lt;Color&gt;
    /// {
    ///     public static readonly Color Blue = Create("Blue");
    ///     public static readonly Color Red = Create("Red");
    ///     public static readonly Color Green = Create("Green");
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="T">The string-valued enum type. (i.e. class Color : StringEnum&lt;Color&gt;)</typeparam>
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static Dictionary<string, T> valueDict = new Dictionary<string, T>();
        protected static T Create(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueDict.Add(value, 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 and takes O(log n). False allows different case but is little bit slower (O(n))</param>
        public static T Parse(string value, bool caseSensitive = true)
        {
            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. False allows different case but is slower: O(n)</param>
        public static T TryParse(string value, bool caseSensitive = true)
        {
            if (value == null) return null;
            if (valueDict.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            if (caseSensitive)
            {
                if (valueDict.TryGetValue(value, out T item))
                    return item;
                else
                    return null;
            }
            else
            {
                // slower O(n) case insensitive search
                return valueDict.FirstOrDefault(f => f.Key.Equals(value, StringComparison.OrdinalIgnoreCase)).Value;
                // Why Ordinal? => https://esmithy.net/2007/10/15/why-stringcomparisonordinal-is-usually-the-right-choice/
            }
        }
    }
Gerardo Grignoli
quelle
3

Hier ist eine weitere Möglichkeit, die Aufgabe des Zuordnens von Zeichenfolgen zu Aufzählungen zu erfüllen:

struct DATABASE {
    public enum enums {NOTCONNECTED, CONNECTED, ERROR}
    static List<string> strings =
        new List<string>() {"Not Connected", "Connected", "Error"};

    public string GetString(DATABASE.enums value) {
        return strings[(int)value];
    }
}

Diese Methode heißt folgendermaßen:

public FormMain() {
    DATABASE dbEnum;

    string enumName = dbEnum.GetString(DATABASE.enums.NOTCONNECTED);
}

Sie können verwandte Aufzählungen in ihrer eigenen Struktur gruppieren. Da diese Methode den Aufzählungstyp verwendet, können Sie mit Intellisense die Liste der Aufzählungen anzeigen, wenn Sie den GetString()Anruf tätigen .

Sie können optional den neuen Operator für die DATABASEStruktur verwenden. Wenn Sie es nicht verwenden, werden die Zeichenfolgen Listerst beim ersten GetString()Aufruf zugewiesen .

Russ
quelle
3

Viele gute Antworten hier, aber in meinem Fall nicht gelöst, was ich von einer "String-Enumeration" wollte, die war:

  1. Verwendbar in einer switch-Anweisung, zB switch (myEnum)
  2. Kann in Funktionsparametern verwendet werden, z. B. foo (myEnum-Typ)
  3. Kann zB myEnum.FirstElement referenziert werden
  4. Ich kann Zeichenfolgen verwenden, z. B. foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 & 4 können tatsächlich mit einem C # -Typedef eines Strings gelöst werden (da Strings in c # umschaltbar sind)

3 kann durch statische const-Strings gelöst werden. Wenn Sie also dieselben Anforderungen haben, ist dies der einfachste Ansatz:

public sealed class Types
{

    private readonly String name;

    private Types(String name)
    {
        this.name = name;

    }

    public override String ToString()
    {
        return name;
    }

    public static implicit operator Types(string str)
    {
        return new Types(str);

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }


    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";
    public const string Folder = "Folder";
    #endregion

}

Dies ermöglicht zum Beispiel:

    public TypeArgs(Types SelectedType)
    {
        Types SelectedType = SelectedType
    }

und

public TypeObject CreateType(Types type)
    {
        switch (type)
        {

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Wobei CreateType mit einer Zeichenfolge oder einem Typ aufgerufen werden kann. Der Nachteil ist jedoch, dass jeder String automatisch eine gültige Aufzählung ist. Dies könnte geändert werden, aber dann würde es eine Art Init-Funktion erfordern ... oder sie explizit intern umwandeln?

Nun , wenn ein int - Wert für Dich wichtig ist (vielleicht für Vergleichsgeschwindigkeit), Sie könnten einige Ideen von Jakub Šturc fantastischer Antwort verwenden und etwas tun , ein bisschen verrückt, das ist mein Stab an sie:

    public sealed class Types
{
    private static readonly Dictionary<string, Types> strInstance = new Dictionary<string, Types>();
    private static readonly Dictionary<int, Types> intInstance = new Dictionary<int, Types>();

    private readonly String name;
    private static int layerTypeCount = 0;
    private int value;
    private Types(String name)
    {
        this.name = name;
        value = layerTypeCount++;
        strInstance[name] = this;
        intInstance[value] = this;
    }

    public override String ToString()
    {
        return name;
    }


    public static implicit operator Types(int val)
    {
        Types result;
        if (intInstance.TryGetValue(val, out result))
            return result;
        else
            throw new InvalidCastException();
    }

    public static implicit operator Types(string str)
    {
        Types result;
        if (strInstance.TryGetValue(str, out result))
        {
            return result;
        }
        else
        {
            result = new Types(str);
            return result;
        }

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }

    public static bool operator ==(Types a, Types b)
    {
        return a.value == b.value;
    }
    public static bool operator !=(Types a, Types b)
    {
        return a.value != b.value;
    }

    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";

    #endregion

}

aber natürlich "Types bob = 4;" wäre bedeutungslos, wenn Sie sie nicht zuerst initialisiert hätten, was den Punkt irgendwie zunichte machen würde ...

Aber theoretisch wäre TypeA == TypeB schneller ...

chrispepper1989
quelle
3

Wenn ich Sie richtig verstehe, können Sie einfach .ToString () verwenden, um den Namen der Aufzählung aus dem Wert abzurufen (vorausgesetzt, sie ist bereits als Aufzählung umgewandelt). Wenn Sie das nackte int hatten (sagen wir aus einer Datenbank oder so), können Sie es zuerst in die Enumeration umwandeln. Mit beiden Methoden erhalten Sie den Namen der Aufzählung.

AuthenticationMethod myCurrentSetting = AuthenticationMethod.FORMS;
Console.WriteLine(myCurrentSetting); // Prints: FORMS
string name = Enum.GetNames(typeof(AuthenticationMethod))[(int)myCurrentSetting-1];
Console.WriteLine(name); // Prints: FORMS

Beachten Sie jedoch, dass bei der zweiten Technik davon ausgegangen wird, dass Sie Ints verwenden und Ihr Index auf 1 basiert (nicht auf 0). Die Funktion GetNames ist im Vergleich auch ziemlich umfangreich. Sie generieren bei jedem Aufruf ein ganzes Array. Wie Sie in der ersten Technik sehen können, wird .ToString () tatsächlich implizit aufgerufen. Beide sind natürlich bereits in den Antworten erwähnt, ich versuche nur, die Unterschiede zwischen ihnen zu klären.

WHol
quelle
3

alter Beitrag aber ...

Die Antwort darauf kann tatsächlich sehr einfach sein. Verwenden Sie die Funktion Enum.ToString ()

Es gibt 6 Überladungen dieser Funktion. Sie können Enum.Tostring ("F") oder Enum.ToString () verwenden, um den Zeichenfolgenwert zurückzugeben. Sie müssen sich um nichts anderes kümmern. Hier ist eine funktionierende Demo

Beachten Sie, dass diese Lösung möglicherweise nicht für alle Compiler funktioniert ( diese Demo funktioniert nicht wie erwartet ), aber zumindest für den neuesten Compiler.

Hammad Khan
quelle
2

Nun, nachdem ich all das oben Genannte gelesen habe, habe ich das Gefühl, dass die Jungs das Problem der Umwandlung von Enumeratoren in Strings überkompliziert haben. Ich mochte die Idee, Attribute über aufgezählte Felder zu haben, aber ich denke, dass Attribute hauptsächlich für Metadaten verwendet werden, aber in Ihrem Fall denke ich, dass alles, was Sie brauchen, eine Art Lokalisierung ist.

public enum Color 
{ Red = 1, Green = 2, Blue = 3}


public static EnumUtils 
{
   public static string GetEnumResourceString(object enumValue)
    {
        Type enumType = enumValue.GetType();
        string value = Enum.GetName(enumValue.GetType(), enumValue);
        string resourceKey = String.Format("{0}_{1}", enumType.Name, value);
        string result = Resources.Enums.ResourceManager.GetString(resourceKey);
        if (string.IsNullOrEmpty(result))
        {
            result = String.Format("{0}", value);
        }
        return result;
    }
}

Wenn wir nun versuchen, die obige Methode aufzurufen, können wir sie so aufrufen

public void Foo()
{
  var col = Color.Red;
  Console.WriteLine (EnumUtils.GetEnumResourceString (col));
}

Sie müssen lediglich eine Ressourcendatei erstellen, die alle Enumeratorwerte und die entsprechenden Zeichenfolgen enthält

Ressourcenname Ressourcenwert
Color_Red Meine Stringfarbe in Rot
Color_Blue Blueeey
Color_Green Hulk Color

Das Schöne daran ist, dass es sehr hilfreich ist, wenn Ihre Anwendung lokalisiert werden muss, da Sie lediglich eine weitere Ressourcendatei mit Ihrer neuen Sprache erstellen müssen! und Voe-la!

Bormagi
quelle
1

Wenn ich in einer solchen Situation bin, schlage ich die folgende Lösung vor.

Und als konsumierende Klasse könnten Sie haben

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyApp.Dictionaries
{
    class Greek
    {

        public static readonly string Alpha = "Alpha";
        public static readonly string Beta = "Beta";
        public static readonly string Gamma = "Gamma";
        public static readonly string Delta = "Delta";


        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();


        static Greek() {
            Dictionary.Add(1, Alpha);
            Dictionary.Add(2, Beta);
            Dictionary.Add(3, Gamma);
            Dictionary.Add(4, Delta);
        }

        public static string getById(int id){
            return Dictionary.GetByFirst(id);
        }

        public static int getByValue(string value)
        {
            return Dictionary.GetBySecond(value);
        }

    }
}

Und Verwenden eines bidirektionalen Wörterbuchs: Basierend darauf ( https://stackoverflow.com/a/255638/986160 ) unter der Annahme, dass die Schlüssel einzelnen Werten im Wörterbuch zugeordnet werden und ähnlich wie ( https://stackoverflow.com/a) sind / 255630/986160 ), aber etwas eleganter. Dieses Wörterbuch ist auch aufzählbar und Sie können von Ints zu Strings hin und her gehen. Außerdem müssen Sie mit Ausnahme dieser Klasse keine Zeichenfolge in Ihrer Codebasis haben.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace MyApp.Dictionaries
{

    class BiDictionary<TFirst, TSecond> : IEnumerable
    {
        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        public void Add(TFirst first, TSecond second)
        {
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond this[TFirst first]
        {
            get { return GetByFirst(first); }
        }

        public TFirst this[TSecond second]
        {
            get { return GetBySecond(second); }
        }

        public TSecond GetByFirst(TFirst first)
        {
            return firstToSecond[first];
        }

        public TFirst GetBySecond(TSecond second)
        {
            return secondToFirst[second];
        }

        public IEnumerator GetEnumerator()
        {
            return GetFirstEnumerator();
        }

        public IEnumerator GetFirstEnumerator()
        {
            return firstToSecond.GetEnumerator();
        }

        public IEnumerator GetSecondEnumerator()
        {
            return secondToFirst.GetEnumerator();
        }
    }
}
Michail Michailidis
quelle
1

Bei größeren Saitensummen können die aufgeführten Beispiele lästig werden. Wenn Sie eine Liste von Statuscodes oder eine Liste anderer auf Zeichenfolgen basierender Aufzählungen wünschen, ist die Verwendung eines Attributsystems ärgerlich, und die Konfiguration einer statischen Klasse mit Instanzen von sich selbst ist ärgerlich. Für meine eigene Lösung verwende ich T4-Vorlagen, um das Erstellen von Aufzählungen mit Zeichenfolgen zu vereinfachen. Das Ergebnis ähnelt der Funktionsweise der HttpMethod-Klasse.

Sie können es so verwenden:

    string statusCode = ResponseStatusCode.SUCCESS; // Automatically converts to string when needed
    ResponseStatusCode codeByValueOf = ResponseStatusCode.ValueOf(statusCode); // Returns null if not found

    // Implements TypeConverter so you can use it with string conversion methods.
    var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(ResponseStatusCode));
    ResponseStatusCode code = (ResponseStatusCode) converter.ConvertFromInvariantString(statusCode);

    // You can get a full list of the values
    bool canIterateOverValues = ResponseStatusCode.Values.Any(); 

    // Comparisons are by value of the "Name" property. Not by memory pointer location.
    bool implementsByValueEqualsEqualsOperator = "SUCCESS" == ResponseStatusCode.SUCCESS; 

Sie beginnen mit einer Enum.tt-Datei.

<#@ include file="StringEnum.ttinclude" #>


<#+
public static class Configuration
{
    public static readonly string Namespace = "YourName.Space";
    public static readonly string EnumName = "ResponseStatusCode";
    public static readonly bool IncludeComments = true;

    public static readonly object Nodes = new
    {
        SUCCESS = "The response was successful.",
        NON_SUCCESS = "The request was not successful.",
        RESOURCE_IS_DISCONTINUED = "The resource requested has been discontinued and can no longer be accessed."
    };
}
#>

Anschließend fügen Sie Ihre Datei StringEnum.ttinclude hinzu.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;

namespace <#= Configuration.Namespace #>
{
    /// <summary>
    /// TypeConverter implementations allow you to use features like string.ToNullable(T).
    /// </summary>
    public class <#= Configuration.EnumName #>TypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var casted = value as string;

            if (casted != null)
            {
                var result = <#= Configuration.EnumName #>.ValueOf(casted);
                if (result != null)
                {
                    return result;
                }
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var casted = value as <#= Configuration.EnumName #>;
            if (casted != null && destinationType == typeof(string))
            {
                return casted.ToString();
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    [TypeConverter(typeof(<#= Configuration.EnumName #>TypeConverter))]
    public class <#= Configuration.EnumName #> : IEquatable<<#= Configuration.EnumName #>>
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T
//---------------------------------------------------------------------------------------------------
<# Write(Helpers.PrintEnumProperties(Configuration.Nodes)); #>

        private static List<<#= Configuration.EnumName #>> _list { get; set; } = null;
        public static List<<#= Configuration.EnumName #>> ToList()
        {
            if (_list == null)
            {
                _list = typeof(<#= Configuration.EnumName #>).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(<#= Configuration.EnumName #>))
                    .Select(x => x.GetValue(null)).OfType<<#= Configuration.EnumName #>>().ToList();
            }

            return _list;
        }

        public static List<<#= Configuration.EnumName #>> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static <#= Configuration.EnumName #> ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N
//---------------------------------------------------------------------------------------------------      
        public string Name { get; private set; }
        public string Description { get; private set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(<#= Configuration.EnumName #> d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(<#= Configuration.EnumName #> other)
        {
            return this.ToString() == other?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}

<#+

public static class Helpers
{
        public static string PrintEnumProperties(object nodes)
        {
            string o = "";
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props = nodesTp.GetProperties().OrderBy(p => p.Name).ToArray();

            for(int i = 0; i < props.Length; i++)
            {
                var prop = props[i];
                if (Configuration.IncludeComments)
                {
                    o += "\r\n\r\n";
                    o += "\r\n        ///<summary>";
                    o += "\r\n        /// "+Helpers.PrintPropertyValue(prop, Configuration.Nodes);
                    o += "\r\n        ///</summary>";
                }

                o += "\r\n        public static readonly "+Configuration.EnumName+" "+prop.Name+ " = new "+Configuration.EnumName+"(){ Name = \""+prop.Name+"\", Description = "+Helpers.PrintPropertyValue(prop, Configuration.Nodes)+ "};";
            }

            o += "\r\n\r\n";

            return o;
        }

        private static Dictionary<string, string> GetValuesMap()
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            var dic = new Dictionary<string,string>();
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                dic[prop.Name] = prop.GetValue(Configuration.Nodes).ToString();
            }
            return dic;
        }

        public static string PrintMasterValuesMap(object nodes)
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            string o = "        private static readonly Dictionary<string, string> ValuesMap = new Dictionary<string, string>()\r\n        {";
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                o += "\r\n            { \""+prop.Name+"\", "+(Helpers.PrintPropertyValue(prop,Configuration.Nodes)+" },");
            }
            o += ("\r\n        };\r\n");

            return o;
        }


        public static string PrintPropertyValue(PropertyInfo prop, object objInstance)
        {
            switch(prop.PropertyType.ToString()){
                case "System.Double":
                    return prop.GetValue(objInstance).ToString()+"D";
                case "System.Float":
                    return prop.GetValue(objInstance).ToString()+"F";
                case "System.Decimal":
                    return prop.GetValue(objInstance).ToString()+"M";
                case "System.Long":
                    return prop.GetValue(objInstance).ToString()+"L";
                case "System.Boolean":
                case "System.Int16":
                case "System.Int32":
                    return prop.GetValue(objInstance).ToString().ToLowerInvariant();
                case "System.String":
                    return "\""+prop.GetValue(objInstance)+"\"";
            }

            return prop.GetValue(objInstance).ToString();
        }

        public static string _ (int numSpaces)
        {
            string o = "";
            for(int i = 0; i < numSpaces; i++){
                o += " ";
            }

            return o;
        }
}
#>

Schließlich kompilieren Sie Ihre Enum.tt-Datei neu und die Ausgabe sieht folgendermaßen aus:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;

namespace YourName.Space
{
    public class ResponseStatusCode
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T 
//---------------------------------------------------------------------------------------------------



        ///<summary>
        /// "The response was successful."
        ///</summary>
        public static readonly ResponseStatusCode SUCCESS = new ResponseStatusCode(){ Name = "SUCCESS", Description = "The response was successful."};


        ///<summary>
        /// "The request was not successful."
        ///</summary>
        public static readonly ResponseStatusCode NON_SUCCESS = new ResponseStatusCode(){ Name = "NON_SUCCESS", Description = "The request was not successful."};


        ///<summary>
        /// "The resource requested has been discontinued and can no longer be accessed."
        ///</summary>
        public static readonly ResponseStatusCode RESOURCE_IS_DISCONTINUED = new ResponseStatusCode(){ Name = "RESOURCE_IS_DISCONTINUED", Description = "The resource requested has been discontinued and can no longer be accessed."};


        private static List<ResponseStatusCode> _list { get; set; } = null;
        public static List<ResponseStatusCode> ToList()
        {
            if (_list == null)
            {
                _list = typeof(ResponseStatusCode).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(ResponseStatusCode))
                    .Select(x => x.GetValue(null)).OfType<ResponseStatusCode>().ToList();
            }

            return _list;
        }

        public static List<ResponseStatusCode> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static ResponseStatusCode ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N 
//---------------------------------------------------------------------------------------------------       
        public string Name { get; set; }
        public string Description { get; set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(ResponseStatusCode d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(ResponseStatusCode a, ResponseStatusCode b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(ResponseStatusCode a, ResponseStatusCode b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}
Pangamma
quelle