954 Stimmen

String-Darstellung einer Enum

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 frage, und nicht die ID 1.

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

Zunächst 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 zu 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 ihn dann wie folgt verwenden:

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

Okay, das funktioniert alles prima, aber ich finde es eine Menge Arbeit. Ich habe mich gefragt, ob es eine bessere Lösung für dieses Problem gibt.

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

5 Stimmen

Auch wenn Sie das vielleicht langatmig finden, so ist es doch ein ziemlich flexibler Weg, um andere Dinge zu erledigen. Wie einer meiner Kollegen bemerkte, könnte dies in vielen Fällen verwendet werden, um Enum Helpers zu ersetzen, die Datenbankcodes auf Enum-Werte usw. abbilden.

3 Stimmen

Es handelt sich um eine "Aufzählung", nicht um einen "Enumerator".

29 Stimmen

MSDN empfiehlt, Attributklassen mit dem Suffix "Attribute" zu versehen. Also "Klasse StringValueAttribute" ;)

7voto

Gerardo Grignoli Punkte 12382

Ich habe eine Basisklasse für die Erstellung von String-bewerteten Enums in .NET erstellt. Es ist nur eine C#-Datei, die Sie in Ihre Projekte kopieren und einfügen oder über ein NuGet-Paket namens StringEnum . GitHub Repo

  • Intellisense schlägt den Enum-Namen vor, wenn die Klasse mit dem xml-Kommentar annotiert ist <completitionlist> . (Funktioniert sowohl in C# als auch in VB)

Intellisense demo

  • Verwendung ähnlich wie bei 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)

Installieren:

  • Fügen Sie die folgende StringEnum-Basisklasse in Ihr Projekt ein. ( neueste Version )
  • Oder installieren Sie StringEnum NuGet-Paket, das auf .Net Standard 1.0 damit es läuft auf .Net Core >= 1.0, .Net Framework >= 4.5, Mono >= 4,6, usw.

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

7voto

Robert Rossney Punkte 91100

Wenn ich mit diesem Problem konfrontiert werde, versuche ich zunächst, eine Antwort auf einige Fragen zu finden:

  • Sind die Namen meiner Enum-Werte für diesen Zweck ausreichend, oder muss ich freundlichere Namen verwenden?
  • Muss ich eine Hin- und Rückreise machen? Das heißt, muss ich Textwerte nehmen und sie in Enum-Werte umwandeln?
  • Ist dies etwas, das ich für viele Enums in meinem Projekt tun müssen, oder nur eine?
  • In welcher Art von UI-Elementen werde ich diese Informationen darstellen - insbesondere, werde ich an die UI binden oder Eigenschaftsblätter verwenden?
  • Muss dies lokalisierbar sein?

Am einfachsten geht das mit Enum.GetValue (und unterstützen Round-Tripping mit Enum.Parse ). Oft lohnt es sich auch, eine TypeConverter wie Steve Mitcham vorschlägt, um die UI-Bindung zu unterstützen. (Es ist nicht notwendig, eine TypeConverter wenn Sie Eigenschaftsblätter verwenden, was einer der Vorteile von Eigenschaftsblättern ist. Obwohl Gott weiß, dass sie ihre eigenen Probleme haben.)

Wenn die Antworten auf die obigen Fragen darauf hindeuten, dass das nicht funktioniert, ist mein nächster Schritt die Erstellung und Befüllung einer statischen Dictionary<MyEnum, string> oder möglicherweise eine Dictionary<Type, Dictionary<int, string>> . Ich neige dazu, den Zwischenschritt "Dekorieren Sie den Code mit Attributen" zu überspringen, weil es in der Regel notwendig ist, die Kommandowerte nach der Bereitstellung zu ändern (oft, aber nicht immer, wegen der Lokalisierung).

6voto

Paula Bean Punkte 460

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

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

Hervorragend!

5voto

Razoomnick Punkte 184

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

Der Code sieht ein wenig hässlich aus, aber die Verwendung dieser Struktur ist ziemlich vorzeigbar.

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, dass, wenn viele solcher Enums benötigt werden, Codegenerierung (z.B. T4) verwendet werden könnte.

4voto

Harvo Punkte 49

Wenn man über das Problem nachdenkt, das wir zu lösen versuchen, brauchen wir überhaupt keine Aufzählung. Wir brauchen ein Objekt, das es ermöglicht, eine bestimmte Anzahl von Werten miteinander zu verknüpfen, mit anderen Worten, eine Klasse zu definieren.

Das typsichere Enum-Muster von Jakub Šturc ist die beste Option, die ich hier sehe.

Sehen Sie es sich an:

  • Sie hat einen privaten Konstruktor, so dass nur die Klasse selbst die zulässigen Werte festlegen kann.
  • Es handelt sich um eine versiegelte Klasse, so dass die Werte nicht durch Vererbung geändert werden können.
  • Es ist typsicher, so dass Ihre Methoden nur diesen Typ benötigen.
  • Der Zugriff auf die Werte hat keine Auswirkungen auf die Leistung.
  • Und schließlich kann es geändert werden, um mehr als zwei Felder miteinander zu verknüpfen, zum Beispiel einen Namen, eine Beschreibung und einen numerischen Wert.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X