Verknüpfen von Aufzählungen mit Zeichenfolgen in C #


363

Ich weiß, dass Folgendes nicht möglich ist, da der Typ der Aufzählung ein int sein muss

enum GroupTypes
{
    TheGroup = "OEM",
    TheOtherGroup = "CMB"
}

Aus meiner Datenbank erhalte ich ein Feld mit nicht umfassenden Codes (the OEMund CMBs). Ich würde dieses Feld zu einem enumoder etwas anderem Verständlichem machen wollen. Denn wenn das Ziel lesbar ist, sollte die Lösung knapp sein.

Welche anderen Optionen habe ich?


mögliches Duplikat von Enum ToString
nawfal

12
Ich bin mir nicht sicher, warum die meisten Antworten nicht nur "const string" verwenden, sondern benutzerdefinierte Klassen erstellen.
CTS_AE

1
Möglicherweise können Sie keine Zeichenfolgen verwenden, aber Sie können Zeichen ganz gut verwenden. Dies ist eine Option, wenn Sie Einzelbuchstabenwerte verwenden können.
T. Sar

1
Wirklich verwirrt darüber, warum die oben von CTS_AE vorgeschlagene Lösung nicht einmal in den drei besten Antworten enthalten ist.
Sinjai

@Sinjai Die explizite Gruppierung verwandter Werte überwiegt die Strafe eines nicht wahrnehmbaren Leistungsverlusts, insbesondere in einer API oder wiederverwendbaren Komponente.
Person27

Antworten:


404

Ich verwende lieber Eigenschaften in einer Klasse anstelle von Methoden, da sie eher enumartig aussehen.

Hier ist ein Beispiel für einen Logger:

public class LogCategory
{
    private LogCategory(string value) { Value = value; }

    public string Value { get; set; }

    public static LogCategory Trace   { get { return new LogCategory("Trace"); } }
    public static LogCategory Debug   { get { return new LogCategory("Debug"); } }
    public static LogCategory Info    { get { return new LogCategory("Info"); } }
    public static LogCategory Warning { get { return new LogCategory("Warning"); } }
    public static LogCategory Error   { get { return new LogCategory("Error"); } }
}

Übergeben Sie typsichere Zeichenfolgenwerte als Parameter:

public static void Write(string message, LogCategory logCategory)
{
    var log = new LogEntry { Message = message };
    Logger.Write(log, logCategory.Value);
}

Verwendungszweck:

Logger.Write("This is almost like an enum.", LogCategory.Info);

4
Der einzige Nachteil, den ich mir vorstellen kann, ist, dass es ein bisschen langsamer wäre, aber dies wäre in den meisten Fällen vernachlässigbar. Und es würde nicht genau das gleiche Verhalten im Editor haben. EG: Das Umschalten würde nicht automatisch einen Fall für jede Möglichkeit ausfüllen. Abgesehen von diesen kleinen Punkten denke ich, dass dies wahrscheinlich eine ziemlich einfache Lösung ist.
Boris Callens

3
Und es ist einfach, Dictionary <LogCategory, Action / Func> als Schalter zu verwenden. :)
Arnis Lapsa

4
@ArnisL. Es reicht nicht aus, als Schlüssel zu arbeiten. Sie müssen Equals () und GetHashCode () überschreiben und den Value-Eigenschaftssetzer privat machen. Trotzdem ist es keine Aufzählung.
Dave Van den Eynde

21
Für meinen eigenen Gebrauch habe ich dieses Konzept erweitert und die ToStringMethode zur Rückgabe überschrieben Value. Und dann implizite Cast-Operatoren zu und von einer Zeichenfolge bereitgestellt. public static implicit operator String(LogCategory category) { return Value; }.
Zarepheth

6
Was ist mit der Verwendung in Switch-Fällen?
David

175

Sie können auch das Erweiterungsmodell verwenden:

public enum MyEnum
{
    [Description("String 1")]
    V1= 1,
    [Description("String 2")]
    V2= 2
} 

Ihre Erweiterungsklasse

public static class MyEnumExtensions
{
    public static string ToDescriptionString(this MyEnum val)
    {
        DescriptionAttribute[] attributes = (DescriptionAttribute[])val
           .GetType()
           .GetField(val.ToString())
           .GetCustomAttributes(typeof(DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : string.Empty;
    }
} 

Verwendungszweck:

MyEnum myLocal = MyEnum.V1;
print(myLocal.ToDescriptionString());

3
Siehe auch stackoverflow.com/questions/4367723/… für eine weitere Erweiterung und zur Beschreibung von Zeichenfolge zu Aufzählung.
Dave

15
Ich kann mir nicht helfen zu denken, dass es aus Performance-Sicht schmerzhaft klingt, die Aufzählung jedes Mal wiederzugeben, wenn Sie den Text anzeigen möchten!
Liath

4
@ Liath - Das `.ToString ()` verwendet bereits Reflexion, so dass Sie mit diesem Ansatz nichts wirklich verlieren und Lesbarkeit gewinnen
James King

1
Könnten Sie die Erweiterung generisch machen, damit sie automatisch auf alle Enums angewendet wird?
Erosebe

3
Um generisch zu machen, verwenden Sie public static string ToDescriptionString(this Enum ...dh ohne explizite Eingabe in MyEnum.
LeeCambl

100

Wie wäre es mit einer statischen Klasse mit Konstanten?

static class GroupTypes
{
  public const string TheGroup = "OEM";
  public const string TheOtherGroup = "CMB";
}

void DoSomething(string groupType)
{
  if(groupType == GroupTypes.TheGroup)
  {
    // Be nice
  }  
  else if (groupType == GroupTypes.TheOtherGroup)
  {
    // Continue to be nice
  }
  else
  {
    // unexpected, throw exception?
  }
}

9
Einverstanden. Ich habe Probleme, den Zweck der komplexeren Lösungen zu erkennen, außer vielleicht, um die resultierende "Aufzählung" umschalten zu können.
Fakeleft

@fakeleft Sie können keinen statischen Klassentyp mit einem generischen Typ (Vorlage) und möglicherweise anderen Einschränkungen verwenden. Ich denke, deshalb bevorzugen die Leute die "komplexeren" Lösungen.
eselk

2
Die Konstanten müssen intern oder öffentlich sein, damit dies funktioniert
Arviman

46
Statische Typen können nicht als Parameter verwendet werden.
Pedro Moreira

2
Wie @PedroMoreira hervorhebt, können Sie keinen GroupTypesArgumenttyp übergeben, da es sich um eine statische Klasse handelt. Das ist das Problem, das sogar Miens Antwort löst. In diesem Fall würden Sie stattdessen haben müssen void DoSomething(string groupType), die dann Mittel , die groupTypehaben könnte überhaupt eine beliebige Zeichenfolge Wert , auch Werte , die Sie nicht erwartet werden, das heißt , Sie für diese ungültigen Typen hergestellt werden müssen und entscheiden , wie sie zu behandeln (zB durch Auslösen einer Ausnahme). Sogar Miens Antwort löst dies, indem die Anzahl der gültigen Eingaben auf die von der LogCategoryKlasse definierten Optionen begrenzt wird .
Pharap

30

Sie können den Elementen in der Aufzählung Attribute hinzufügen und dann mithilfe der Reflektion die Werte aus den Attributen abrufen.

Sie müssten den "Feld" -Spezifizierer verwenden, um die Attribute wie folgt anzuwenden:

enum GroupTypes
{
    [field:Description("OEM")]
    TheGroup,

    [field:Description("CMB")]
    TheOtherGroup
}

Sie würden dann über die statischen Felder des Aufzählungstyps (in diesem Fall GroupTypes) nachdenken und DescriptionAttributeden Wert für den Wert ermitteln, den Sie mithilfe von Reflection gesucht haben:

public static DescriptionAttribute GetEnumDescriptionAttribute<T>(
    this T value) where T : struct
{
    // The type of the enum, it will be reused.
    Type type = typeof(T);

    // If T is not an enum, get out.
    if (!type.IsEnum) 
        throw new InvalidOperationException(
            "The type parameter T must be an enum type.");

    // If the value isn't defined throw an exception.
    if (!Enum.IsDefined(type, value))
        throw new InvalidEnumArgumentException(
            "value", Convert.ToInt32(value), type);

    // Get the static field for the value.
    FieldInfo fi = type.GetField(value.ToString(), 
        BindingFlags.Static | BindingFlags.Public);

    // Get the description attribute, if there is one.
    return fi.GetCustomAttributes(typeof(DescriptionAttribute), true).
        Cast<DescriptionAttribute>().SingleOrDefault();
}

Ich habe mich dafür entschieden, das DescriptionAttributeobige selbst zurückzugeben, falls Sie feststellen möchten, ob das Attribut überhaupt angewendet wird oder nicht.


Obwohl ich mich für komplexere Situationen daran erinnern werde, ist es für eine Situation mit dem Komplexitätsgrad dessen, was ich im OP angegeben habe, ziemlich komplex
Boris Callens

26

Sie können es tatsächlich sehr einfach tun. Verwenden Sie den folgenden Code.

enum GroupTypes
{
   OEM,
   CMB
};

Wenn Sie dann den Zeichenfolgenwert jedes Enum-Elements abrufen möchten, verwenden Sie einfach die folgende Codezeile.

String oemString = Enum.GetName(typeof(GroupTypes), GroupTypes.OEM);

Ich habe diese Methode in der Vergangenheit erfolgreich verwendet, und ich habe auch eine Konstantenklasse verwendet, um Zeichenfolgenkonstanten zu halten. Beide funktionieren ziemlich gut, aber ich bevorzuge dies.


Ich dachte das Gleiche, aber es muss einen Haken geben ... Sonst würde ich vermuten, dass mehr Leute dies vorschlagen würden (vielleicht bin ich nur paranoid).
Matthijs Wessels

Der einzige Haken, den ich kenne, ist, dass ich glaube, dass es Reflexion verwendet, um die Zeichenfolge herauszufinden. Wenn ich also nur nach einer Lösung suche, um eine konstante Zeichenfolge zu verfolgen, verwende ich normalerweise eine Klasse, um den Großteil meiner konstanten Zeichenfolgen zu speichern. Wenn ich jedoch eine Situation habe, in der eine Aufzählung die richtige Lösung ist (unabhängig davon, ob ich eine beschreibende Zeichenfolge für meine Aufzählungselemente erhalte), verwende ich den Aufzählungswert wie beschrieben, anstatt irgendwo eine zusätzliche Zeichenfolge zum Verwalten zu haben.
Arthur C

+1 Dies ist die beste und einfachste Antwort und hat auch hier hohe Stimmen , um dies zu beweisen. Das Erweiterungsmodell ist nur dann besser, wenn Sie Leerzeichen im Text benötigen (weitere Details hier ).
SharpC

14
Nein, hiermit wird nur der Name eines Aufzählungswerts abgerufen und keinem Aufzählungswert eine Zeichenfolge zugewiesen. Das Ziel des OP ist es, eine Zeichenfolge zu haben, die sich vom Aufzählungswert unterscheidet, z. B.: TheGroup = "OEM", TheOtherGroup = "CMB".
Tim Autin

3
Ich stimme dem Kommentar von @ Tim zu, dies ist nicht das, was das OP versucht. Wenn Sie sich fragen, was ein Anwendungsfall dafür ist, stellen Sie sich eine Situation vor, in der ein Gerät Zeichenfolgen als Befehle verwendet, aber es muss auch eine "vom Menschen lesbare" Version des Befehls vorhanden sein. Ich brauchte dies, um etwas wie "Firmware aktualisieren" mit dem Befehl "UPDATEFW" zu verknüpfen.
JYelton

20

Versuchen Sie, einer statischen Klasse Konstanten hinzuzufügen. Sie erhalten keinen Typ, aber Sie haben lesbare, organisierte Konstanten:

public static class GroupTypes {

    public const string TheGroup = "OEM";
    public const string TheOtherGroup = "CMB";

}

3
Es ist schwierig, vom Code zurück zum beschreibenden Namen zu gelangen. Sie müssten alle const-Felder reflektieren, um nach einer Übereinstimmung zu suchen.
andleer

1
@andleer Ich verstehe dein Anliegen nicht. Dies ist die Lösung, die ich benutze.
VSO

Ja, genau das wollte ich. Und dies ist die prägnanteste / eleganteste Lösung, die ich sehe, als würde ich eine Aufzählung mit int-Werten definieren - aber stattdessen mit Zeichenfolgenwerten. 100% perfekt.
Tschad

3
Das Problem dabei ist, dass es nicht als Enum in dem Sinne funktioniert, dass wir keinen separaten Typ mit einer endlichen Liste von Werten haben. Eine Funktion, die diese erwartet, könnte mit Freiformzeichenfolgen verwendet werden, die fehleranfällig sind.
Juan Martinez

14

Erstellen Sie eine zweite Aufzählung für Ihre Datenbank, die Folgendes enthält:

enum DBGroupTypes
{
    OEM = 0,
    CMB = 1
}

Jetzt können Sie Enum.Parse verwenden, um den korrekten DBGroupTypes-Wert aus den Zeichenfolgen "OEM" und "CMB" abzurufen. Sie können diese dann in int konvertieren und die richtigen Werte aus der richtigen Aufzählung abrufen, die Sie in Ihrem Modell weiter verwenden möchten.


Dies scheint ein zusätzlicher Schritt in diesem Prozess zu sein. Warum nicht eine Klasse, die alles erledigt?
C. Ross

11
Im Gegensatz zur Verwendung von Attributen und Reflexion?
Dave Van den Eynde

13

Verwenden Sie eine Klasse.

Edit: Besseres Beispiel

class StarshipType
{
    private string _Name;
    private static List<StarshipType> _StarshipTypes = new List<StarshipType>();

    public static readonly StarshipType Ultralight = new StarshipType("Ultralight");
    public static readonly StarshipType Light = new StarshipType("Light");
    public static readonly StarshipType Mediumweight = new StarshipType("Mediumweight");
    public static readonly StarshipType Heavy = new StarshipType("Heavy");
    public static readonly StarshipType Superheavy = new StarshipType("Superheavy");

    public string Name
    {
        get { return _Name; }
        private set { _Name = value; }
    }

    public static IList<StarshipType> StarshipTypes
    {
        get { return _StarshipTypes; }
    }

    private StarshipType(string name, int systemRatio)
    {
        Name = name;
        _StarshipTypes.Add(this);
    }

    public static StarshipType Parse(string toParse)
    {
        foreach (StarshipType s in StarshipTypes)
        {
            if (toParse == s.Name)
                return s;
        }
        throw new FormatException("Could not parse string.");
    }
}

1
Es ist schwierig, vom Code zurück zum beschreibenden Namen zu gelangen. Sie müssten alle const-Felder reflektieren, um nach einer Übereinstimmung zu suchen.
andleer

1
Ich weiß, worauf du hinauswillst. Ich werde eine Version hochladen, die später tatsächlich funktioniert, aber ich gebe zu, dass sie ziemlich schwer ist.
C. Ross

Meine Version basiert auf der Lösung von C. Ross stackoverflow.com/a/48441114/3862615
Roman M.

7

Eine andere Möglichkeit, das Problem zu lösen, besteht darin, eine Aufzählung und ein Array von Zeichenfolgen zu haben, die die Aufzählungswerte der Liste der Zeichenfolgen zuordnen:

public enum GroupTypes
{
    TheGroup  = 0,
    TheOtherGroup 
}

string[] GroupTypesStr = {
    "OEM",
    "CMB"
};

Sie können es so etwas verwenden:

Log.Write(GroupTypesStr[(int)GroupTypes.TheOtherGroup]);

Es wird CMB aufgefordert

PROS:

  1. Einfacher und sauberer Code.
  2. Hohe Leistung (insbesondere im Vergleich zu Ansätzen, bei denen Klassen verwendet werden)

Nachteile:

  1. Neigt dazu, die Liste beim Bearbeiten durcheinander zu bringen, aber für eine kurze Liste ist dies in Ordnung.

6

Hier ist die Erweiterungsmethode, mit der ich den Aufzählungswert als Zeichenfolge abgerufen habe. Hier ist zuerst die Aufzählung.

public enum DatabaseEnvironment
{
    [Description("AzamSharpBlogDevDatabase")]
    Development = 1, 
    [Description("AzamSharpBlogQADatabase")]
    QualityAssurance = 2, 
    [Description("AzamSharpBlogTestDatabase")] 
    Test = 3
}

Das Description-Attribut stammt von System.ComponentModel.

Und hier ist meine Erweiterungsmethode:

public static string GetValueAsString(this DatabaseEnvironment environment) 
{
    // get the field 
    var field = environment.GetType().GetField(environment.ToString());
    var customAttributes = field.GetCustomAttributes(typeof (DescriptionAttribute), false);

    if(customAttributes.Length > 0)
    {
        return (customAttributes[0] as DescriptionAttribute).Description;  
    }
    else
    {
        return environment.ToString(); 
    }
}

Jetzt können Sie mit dem folgenden Code auf die Aufzählung als Zeichenfolgenwert zugreifen:

[TestFixture]
public class when_getting_value_of_enum
{
    [Test]
    public void should_get_the_value_as_string()
    {
        Assert.AreEqual("AzamSharpBlogTestDatabase",DatabaseEnvironment.Test.GetValueAsString());  
    }
}

5

Haben Sie eine Nachschlagetabelle mit einem Wörterbuch in Betracht gezogen?

enum GroupTypes
{
    TheGroup,
    TheOtherGroup
}

Dictionary<string, GroupTypes> GroupTypeLookup = new Dictionary<string, GroupTypes>();
// initialize lookup table:
GroupTypeLookup.Add("OEM", TheGroup);
GroupTypeLookup.Add("CMB", TheOtherGroup);

Sie können dann GroupTypeLookup.TryGetValue () verwenden, um eine Zeichenfolge beim Lesen nachzuschlagen.


Wie erhalten Sie leicht den Schlüssel für einen bestimmten Wert?
Eglasius

Die Frage fragte nicht nach dem anderen Weg. Aber es wäre einfach genug, ein anderes Wörterbuch zu erstellen, das in die andere Richtung geht. Das heißt, Dictionary <GroupTypes, string>.
Jim Mischel

4
public class DataType
{
    private readonly string value;
    private static readonly Dictionary<string, DataType> predefinedValues;

    public static readonly DataType Json = new DataType("json");
    public static readonly DataType Xml = new DataType("xml");
    public static readonly DataType Text = new DataType("text");
    public static readonly DataType Html = new DataType("html");
    public static readonly DataType Binary = new DataType("binary");

    static DataType()
    {
        predefinedValues = new Dictionary<string, DataType>();
        predefinedValues.Add(Json.Value, Json);
        predefinedValues.Add(Xml.Value, Xml);
        predefinedValues.Add(Text.Value, Text);
        predefinedValues.Add(Html.Value, Html);
        predefinedValues.Add(Binary.Value, Binary);
    }

    private DataType(string value)
    {
        this.value = value;
    }

    public static DataType Parse(string value)
    {
        var exception = new FormatException($"Invalid value for type {nameof(DataType)}");
        if (string.IsNullOrEmpty(value))
            throw exception;

        string key = value.ToLower();
        if (!predefinedValues.ContainsKey(key))
            throw exception;

        return predefinedValues[key];
    }

    public string Value
    {
        get { return value; }
    }
}

3

C # unterstützt keine aufgezählten Zeichenfolgen, aber in den meisten Situationen können Sie eine Liste oder ein Wörterbuch verwenden, um den gewünschten Effekt zu erzielen.

ZB So drucken Sie Pass / Fail-Ergebnisse:

List<string> PassFail = new List<string> { "FAIL", "PASS" };
bool result = true;
Console.WriteLine("Test1: " + PassFail[result.GetHashCode()]);

2

Ich würde es zu einer Klasse machen und eine Aufzählung ganz vermeiden. Und dann können Sie mit einem Typhandler das Objekt erstellen, wenn Sie es aus der Datenbank holen.

IE:

public class Group
{
    public string Value{ get; set; }
    public Group( string value ){ Value = value; } 
    public static Group TheGroup() { return new Group("OEM"); }
    public static Group OtherGroup() { return new Group("CMB"); }

}

2

Ich würde einfach ein Wörterbuch erstellen und den Code als Schlüssel verwenden.

Bearbeiten: Um den Kommentar zu einer umgekehrten Suche (Finden des Schlüssels) zu adressieren, wäre dies nicht besonders effizient. Wenn dies notwendig ist, würde ich eine neue Klasse schreiben, um damit umzugehen.


Können Sie auch leicht einen Schlüssel für einen bestimmten Wert greifen?
Eglasius

Zu C. Ross - Ich bin mir nicht sicher, was du meinst. Sie können die Werte aus einer Datenbank einlesen und das Wörterbuch dynamisch füllen.
Jhale

2

Meine erste Frage - Haben Sie Zugriff auf die Datenbank selbst? Dies sollte in der Datenbank normalisiert werden, andernfalls ist jede Lösung fehleranfällig. Nach meiner Erfahrung werden in Datenfeldern mit "OEM" und "CMB" häufig Dinge wie "OEM" und andere "Mistdaten" im Laufe der Zeit gemischt. Wenn Sie sie normalisieren können, können Sie den Schlüssel verwenden in der Tabelle mit den Elementen als Aufzählung, und Sie sind fertig, mit einer viel saubereren Struktur.

Wenn das nicht verfügbar ist, würde ich Ihre Aufzählung erstellen und eine Klasse erstellen, um Ihre Zeichenfolge für Sie in die Aufzählung zu analysieren. Dies würde Ihnen zumindest eine gewisse Flexibilität bei der Behandlung von nicht standardmäßigen Einträgen und viel mehr Flexibilität bei der Überfüllung oder Behandlung von Fehlern geben, als wenn Sie eine der Problemumgehungen mit Enum.Parse / Reflection / etc. Ein Wörterbuch würde funktionieren, könnte aber zusammenbrechen, wenn Sie jemals Fallprobleme usw. haben.

Ich würde empfehlen, eine Klasse zu schreiben, damit Sie Folgendes tun können:

// I renamed this to GroupType, since it sounds like each element has a single type...
GroupType theType = GroupTypeParser.GetGroupType(theDBString);

Dadurch bleibt der größte Teil Ihrer Lesbarkeit erhalten, ohne dass die Datenbank geändert werden muss.


2

Wenn ich das richtig verstehe, benötigen Sie eine Konvertierung von Zeichenfolge in Aufzählung:

enum GroupTypes {
    Unknown = 0,
    OEM = 1,
    CMB = 2
}
static GroupTypes StrToEnum(string str){
    GroupTypes g = GroupTypes.Unknown;
    try {
        object o = Enum.Parse(typeof(GroupTypes), str, true);
        g = (GroupTypes)(o ?? 0);
    } catch {
    }
    return g;
}
// then use it like this
GroupTypes g1 = StrToEnum("OEM");
GroupTypes g2 = StrToEnum("bad value");

Wenn Sie möchten, können Sie es mit Generika für den Aufzählungstyp ausgefallener machen.


2

In VS 2015 können Sie nameof verwenden

public class LogCategory
{
    public static string Trace;
    public static string Debug;
    public static string Info;
    public static string Warning;
    public static string Error;
}

Verwendungszweck:

Logger.Write("This is almost like an enum.", nameof(LogCategory.Info));

2

Eine kleine Änderung an der Glennular Extension-Methode, damit Sie die Erweiterung auch für andere Zwecke als nur für ENUM verwenden können.

using System;
using System.ComponentModel;
namespace Extensions {
    public static class T_Extensions {
        /// <summary>
        /// Gets the Description Attribute Value
        /// </summary>
        /// <typeparam name="T">Entity Type</typeparam>
        /// <param name="val">Variable</param>
        /// <returns>The value of the Description Attribute or an Empty String</returns>
        public static string Description<T>(this T t) {
            DescriptionAttribute[] attributes = (DescriptionAttribute[])t.GetType().GetField(t.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return attributes.Length > 0 ? attributes[0].Description : string.Empty;
        }
    }
}

Oder mit Linq

using System;
using System.ComponentModel;
using System.Linq;

namespace Extensions {


public static class T_Extensions {
        public static string Description<T>(this T t) =>
            ((DescriptionAttribute[])t
            ?.GetType()
            ?.GetField(t?.ToString())
            ?.GetCustomAttributes(typeof(DescriptionAttribute), false))
            ?.Select(a => a?.Description)
            ?.FirstOrDefault() 
            ?? string.Empty;  
    }
}

2

Nach der Antwort von @Even Mien habe ich versucht, ein bisschen weiter zu gehen und es generisch zu machen. Ich scheine fast da zu sein, aber ein Fall widersteht immer noch und ich kann meinen Code wahrscheinlich ein bisschen vereinfachen.
Ich poste es hier, wenn jemand sieht, wie ich es verbessern und vor allem dafür sorgen kann, dass es funktioniert, da ich es nicht aus einem String zuweisen kann

Bisher habe ich folgende Ergebnisse:

        Console.WriteLine(TestEnum.Test1);//displays "TEST1"

        bool test = "TEST1" == TestEnum.Test1; //true

        var test2 = TestEnum.Test1; //is TestEnum and has value

        string test3 = TestEnum.Test1; //test3 = "TEST1"

        var test4 = TestEnum.Test1 == TestEnum.Test2; //false
         EnumType<TestEnum> test5 = "TEST1"; //works fine

        //TestEnum test5 = "string"; DOESN'T compile .... :(:(

Wo die Magie passiert:

public abstract  class EnumType<T>  where T : EnumType<T>   
{

    public  string Value { get; set; }

    protected EnumType(string value)
    {
        Value = value;
    }


    public static implicit operator EnumType<T>(string s)
    {
        if (All.Any(dt => dt.Value == s))
        {
            Type t = typeof(T);

            ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,null, new Type[] { typeof(string) }, null);

            return (T)ci.Invoke(new object[] {s});
        }
        else
        {
            return null;
        }
    }

    public static implicit operator string(EnumType<T> dt)
    {
        return dt?.Value;
    }


    public static bool operator ==(EnumType<T> ct1, EnumType<T> ct2)
    {
        return (string)ct1 == (string)ct2;
    }

    public static bool operator !=(EnumType<T> ct1, EnumType<T> ct2)
    {
        return !(ct1 == ct2);
    }


    public override bool Equals(object obj)
    {
        try
        {
            return (string)obj == Value;
        }
        catch
        {
            return false;
        }
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public static IEnumerable<T> All
     => typeof(T).GetProperties()
       .Where(p => p.PropertyType == typeof(T))
       .Select(x => (T)x.GetValue(null, null));



}

Ich muss dies erst dann für meine Aufzählungen deklarieren:

public class TestEnum : EnumType<TestEnum> 
{

    private TestEnum(string value) : base(value)
    {}

    public static TestEnum Test1 { get { return new TestEnum("TEST1"); } }
    public static TestEnum Test2 { get { return new TestEnum("TEST2"); } }
}

Vielen Dank für diesen tollen Job, ich habe lange nach einem solchen Ansatz gesucht. Ich denke, Sie sollten 1000 Punkte dafür bekommen
user3492977

Oh, danke für diesen Kommentar und danke, dass du mich daran erinnert hast. Ich hatte c # zwei Jahre lang nicht verwendet, als ich diesen Code geschrieben habe. Ich sollte bald darauf zurückkommen!
Lomithrani

@ user3492977 Ich bin endlich darauf zurückgekommen und habe es voll funktionsfähig gemacht. Ich bin jedoch immer noch zweifelhaft, ob es eine großartige Idee oder eine nutzlose Sache ist: D stackoverflow.com/questions/62043138/…
Lomithrani

2

Neu in .Net Core 3.0 / C # 8.0 (wenn Ihre Arbeitsumgebung es Ihnen ermöglicht, Ihr Projekt zu aktualisieren) ist eine kurze switch-Anweisung, die etwas aufzählend aussieht. Letztendlich ist es die gleiche langweilige Switch-Anweisung, die wir seit Jahren verwenden.

Der einzige wirkliche Unterschied besteht darin, dass die switch-Anweisung einen neuen Anzug bekommen hat.

public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
    Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
    Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
    Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
    Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
    Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
    Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
    Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
    _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};

Sie werden feststellen, dass der Code, über den ich von hier kopiert habe , tatsächlich eine Aufzählung als Parameter verwendet.

Es ist nicht genau das, was Sie wollen (und vertrauen Sie mir, ich wollte schon seit langer Zeit etwas Ähnliches wie das OP), aber ich habe tatsächlich das Gefühl, dass dies ein Olivenzweig von MS ist. JMO.

Hoffe es hilft jemandem!


2

Ich habe eine Struktur verwendet, auf die in einer früheren Antwort hingewiesen wurde, aber jede Komplexität beseitigt. Für mich war dies am ähnlichsten wie das Erstellen einer Aufzählung von Zeichenfolgen. Es wird auf die gleiche Weise verwendet, wie eine Aufzählung verwendet wird.

    struct ViewTypes
    {
        public const string View1 = "Whatever string you like";
        public const string View2 = "another string";
    }

Anwendungsbeispiel:

   switch( some_string_variable )
   {
      case ViewTypes.View1: /* do something */ break;
      case ViewTypes.View2: /* do something else */ break;
   }

1

Ich habe sogar ein paar Enums implementiert, wie von @Even (via class Xund public static XMitglieder) vorgeschlagen, um später herauszufinden, dass es heutzutage ab .Net 4.5 das Richtige gibt ToString() Methode gibt.

Jetzt setze ich alles wieder in Aufzählungen um.


1

Dies ist eine Möglichkeit, es als stark typisierten Parameter oder als Zeichenfolge zu verwenden :

public class ClassLikeEnum
{
    public string Value
    {
        get;
        private set;
    }

    ClassLikeEnum(string value) 
    {
        Value = value;
    }

    public static implicit operator string(ClassLikeEnum c)
    {
        return c.Value;
    }

    public static readonly ClassLikeEnum C1 = new ClassLikeEnum("RandomString1");
    public static readonly ClassLikeEnum C2 = new ClassLikeEnum("RandomString2");
}

1

Sie können zwei Aufzählungen verwenden. Eine für die Datenbank und die andere für die Lesbarkeit.

Sie müssen nur sicherstellen, dass sie synchron bleiben, was wie geringe Kosten erscheint. Sie müssen die Werte nicht festlegen, sondern nur die Positionen gleich festlegen. Durch das Festlegen der Werte wird jedoch deutlich, dass die beiden Aufzählungen zusammenhängen, und es wird verhindert, dass Fehler die Aufzählungselemente neu anordnen. Ein Kommentar informiert die Wartungsmannschaft darüber, dass diese miteinander zusammenhängen und synchron gehalten werden müssen.

// keep in sync with GroupTypes
public enum GroupTypeCodes
{
    OEM,
    CMB
}

// keep in sync with GroupTypesCodes
public enum GroupTypes
{
    TheGroup = GroupTypeCodes.OEM,
    TheOtherGroup = GroupTypeCodes.CMB
}

Um es zu verwenden, konvertieren Sie einfach zuerst in den Code:

GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = ((GroupTypeCodes)myGroupType).ToString();

Wenn Sie es dann noch komfortabler machen möchten, können Sie eine Erweiterungsfunktion hinzufügen, die nur für diese Art von Aufzählung funktioniert:

public static string ToString(this GroupTypes source)
{
    return ((GroupTypeCodes)source).ToString();
}

und Sie können dann einfach tun:

GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = myGroupType.ToString();

Das ist eine schlechte Praxis: Bei einer Abhängigkeit kann enumeine beabsichtigte Wertänderung in einer die andere unbeabsichtigt durcheinander bringen.
Lorenz Lo Sauer

1

Ich suchte im Grunde nach der Reflection-Antwort von @ArthurC

Um seine Antwort ein wenig zu erweitern, können Sie sie noch besser machen, indem Sie eine generische Funktion haben:

    // If you want for a specific Enum
    private static string EnumStringValue(GroupTypes e)
    {
        return EnumStringValue<GroupTypes>(e);
    }

    // Generic
    private static string EnumStringValue<T>(T enumInstance)
    {
        return Enum.GetName(typeof(T), enumInstance);
    } 

Dann können Sie einfach alles einwickeln, was Sie haben

EnumStringValue(GroupTypes.TheGroup) // if you incorporate the top part

oder

EnumStringValue<GroupTypes>(GroupTypes.TheGroup) // if you just use the generic

1

Entnommen aus @EvenMien und in einigen Kommentaren hinzugefügt. (Auch für meinen eigenen Anwendungsfall)

public struct AgentAction
{
    private AgentAction(string value) { Value = value; }

    public string Value { get; private set; }

    public override string ToString()
    {
        return this.Value;
    }

    public static AgentAction Login = new AgentAction("Logout");
    public static AgentAction Logout = new AgentAction("Logout");

    public static implicit operator string(AgentAction action) { return action.ToString(); }
}

1

Hinzufügen dieser Klasse

public class DatabasePreference {
    public DatabasePreference([CallerMemberName] string preferenceName = "") {
        PreferenceName = preferenceName;
    }
    public string PreferenceName;
}

Diese Arbeit dient dazu CallerMemberName, die Codierung zu minimieren

Verwenden von:

//Declare names
public static DatabasePreference ScannerDefaultFlashLight = new DatabasePreference();
public static DatabasePreference ScannerQrCodes = new DatabasePreference();
public static DatabasePreference Scanner1dCodes = new DatabasePreference();

Probier es aus:

Console.WriteLine(ScannerDefaultFlashLight.PreferenceName);
Console.WriteLine(ScannerDefaultFlashLight.Scanner1dCodes);

Ausgabe:

ScannerDefaultFlashLight
Scanner1dCodes

0

Aufgrund anderer Meinungen habe ich mir das ausgedacht. Durch diesen Ansatz wird vermieden, dass Sie .Value eingeben müssen, wenn Sie den konstanten Wert erhalten möchten.

Ich habe eine Basisklasse für alle String-Enums wie diese:

using System;
using Newtonsoft.Json;

[JsonConverter(typeof(ConstantConverter))]
public class StringEnum: IConvertible
{
    public string Value { get; set; }

    protected StringEnum(string value)
    {
        Value = value;
    }

    public static implicit operator string(StringEnum c)
    {
        return c.Value;
    }
    public string ToString(IFormatProvider provider)
    {
        return Value;
    }

    public TypeCode GetTypeCode()
    {
        throw new NotImplementedException();
    }

    public bool ToBoolean(IFormatProvider provider)
    {
        throw new NotImplementedException();
    }
    //The same for all the rest of IConvertible methods
}

Der JsonConverter sieht folgendermaßen aus:

using System;
using Newtonsoft.Json;

class ConstantConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
        {
            serializer.Serialize(writer, null);
        }
        else
        {
            serializer.Serialize(writer, value.ToString());
        }
    }
}

Und eine tatsächliche String-Aufzählung wird ungefähr so ​​aussehen:

public sealed class Colors : StringEnum
{
    public static Colors Red { get { return new Catalog("Red"); } }
    public static Colors Yellow { get { return new Catalog("Yellow"); } }
    public static Colors White { get { return new Catalog("White"); } }

    private Colors(string value) : base(value) { }
}

Und damit können Sie einfach Color.Red verwenden, um sogar in json zu serialisieren, ohne die Value-Eigenschaft zu verwenden


0

Ich brauchte nichts Robustes wie das Speichern der Zeichenfolge in Attributen. Ich musste nur so etwas wie MyEnum.BillEveryWeek"Rechnung jede Woche" oder MyEnum.UseLegacySystem"Legacy-System verwenden" umwandeln - im Grunde die Aufzählung durch die Kamelhülle in einzelne Kleinbuchstaben aufteilen.

public static string UnCamelCase(this Enum input, string delimiter = " ", bool preserveCasing = false)
{
    var characters = input.ToString().Select((x, i) =>
    {

       if (i > 0 && char.IsUpper(x))
       {
           return delimiter + x.ToString(CultureInfo.InvariantCulture);
       }
       return x.ToString(CultureInfo.InvariantCulture);

    });

    var result = preserveCasing
       ? string.Concat(characters)
       : string.Concat(characters).ToLower();

    var lastComma = result.LastIndexOf(", ", StringComparison.Ordinal);

    if (lastComma > -1)
    {
       result = result.Remove(lastComma, 2).Insert(lastComma, " and ");
    }

    return result;
}

MyEnum.UseLegacySystem.UnCamelCase() Ausgänge "Legacy-System verwenden"

Wenn Sie mehrere Flags gesetzt haben, wird dies in einfaches Englisch umgewandelt (durch Kommas getrennt, mit Ausnahme eines "und" anstelle des letzten Kommas).

var myCustomerBehaviour = MyEnum.BillEveryWeek | MyEnum.UseLegacySystem | MyEnum.ChargeTaxes;

Console.WriteLine(myCustomerBehaviour.UnCamelCase());
//outputs "bill every week, use legacy system and charge taxes"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.