Kojarzenie wyliczeń z ciągami w C #


361

Wiem, że następujące nie jest możliwe, ponieważ typ wyliczenia musi być liczbą całkowitą

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

Z mojej bazy danych otrzymuję pole z niekompletnymi kodami ( OEMi CMB). Chciałbym przekształcić to pole w enumcoś innego lub zrozumiałego. Ponieważ jeśli celem jest czytelność, rozwiązanie powinno być zwięzłe.

Jakie inne opcje mam?


możliwy duplikat Enum ToString
nawfal

12
Nie jestem pewien, dlaczego większość odpowiedzi nie używa tylko „const string”, a zamiast tego tworzy niestandardowe klasy.
CTS_AE

1
Możesz nie być w stanie używać łańcuchów, ale możesz dobrze używać znaków. Jest to opcja, jeśli możesz użyć wartości jednoliterowych.
T. Sar

1
Naprawdę zdezorientowany, dlaczego rozwiązanie zaproponowane powyżej przez CTS_AE nie znajduje się nawet w trzech pierwszych odpowiedziach.
Sinjai,

@ Sinjai Jawne grupowanie powiązanych wartości przeważy karę za niezauważalną utratę wydajności, szczególnie w interfejsie API lub komponencie wielokrotnego użytku.
person27

Odpowiedzi:


402

Lubię używać właściwości w klasie zamiast metod, ponieważ wyglądają bardziej jak wyliczenia.

Oto przykład rejestratora:

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

Przekaż wartości ciągu znaków bezpieczne dla typu jako parametr:

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

Stosowanie:

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

4
Jedyną wadą, którą mogę wymyślić, jest to, że byłoby trochę wolniejsze, ale w większości przypadków byłoby to zaniedbywalne. I to nie miałoby dokładnie takiego samego zachowania w edytorze. EG: przełączenie tego nie spowoduje automatycznego wypełnienia skrzynki dla każdej możliwości. Poza tymi drobnymi kwestiami myślę, że jest to prawdopodobnie dość proste rozwiązanie.
Boris Callens,

3
I łatwo jest używać Dictionary <LogCategory, Action / Func> jako przełącznika. :)
Arnis Lapsa,

4
@ArnisL. Nie wystarczy pracować jako klucz, musisz przesłonić Equals () i GetHashCode (), a chcesz ustawić prywatność setera właściwości Value. Ale to nie jest wyliczenie.
Dave Van den Eynde

21
Na własny użytek rozwinąłem tę koncepcję, zastępując ToStringmetodę powrotu Value. A następnie zapewnił niejawne operatory rzutowania do i z łańcucha. public static implicit operator String(LogCategory category) { return Value; }.
Zarepheth

6
Co z wykorzystaniem tego w skrzynkach przełączników?
David

176

Możesz także użyć modelu rozszerzenia:

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

Twoja klasa rozszerzenia

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

stosowanie:

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

3
Zobacz także stackoverflow.com/questions/4367723/... w celu uzyskania innego rozszerzenia i od łańcucha do wyliczenia w drodze opisu.
Dave

14
Nie mogę przestać myśleć, że odzwierciedlenie wyliczenia za każdym razem, gdy chcesz, wyświetla tekst brzmi trochę bolesnie z punktu widzenia wydajności!
Liath,

4
@Liath - `.ToString ()` już używa refleksji, więc tak naprawdę nic nie tracisz dzięki takiemu podejściu i zyskujesz czytelność
James King

1
Czy możesz sprawić, by Rozszerzenie było ogólne, aby automatycznie stosowało się do wszystkich Enums?
erosebe

3
Aby zrobić ogólny, użyj public static string ToDescriptionString(this Enum ...np. Bez wyraźnego wpisywania do MyEnum.
LeeCambl

100

Co powiesz na użycie klasy statycznej ze stałymi?

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
Zgoda. Mam problem z dostrzeżeniem celu bardziej złożonych rozwiązań, z wyjątkiem być może możliwości przełączenia wynikowego „wyliczenia”.
fakeleft

@fakeleft nie możesz użyć statycznego typu klasy z typem ogólnym (szablonem) i być może z innymi ograniczeniami, myślę, że właśnie dlatego ludzie wolą „bardziej złożone” rozwiązania.
eselk

2
Stałe muszą być wewnętrzne lub publiczne, aby to zadziałało
arviman

46
Typów statycznych nie można używać jako parametrów.
Pedro Moreira

2
Jak wskazuje @PedroMoreira, nie można podać GroupTypesjako argumentu, ponieważ jest to klasa statyczna. To jest problem, który rozwiązuje nawet odpowiedź Mien. W takim przypadku musiałbyś zamiast tego mieć void DoSomething(string groupType), co oznacza, że groupTypemoże mieć dowolną wartość ciągu , nawet wartości, których się nie spodziewasz, co oznacza, że ​​musisz być przygotowany na te nieprawidłowe typy i zdecydować, jak sobie z nimi poradzić (np. poprzez zgłoszenie wyjątku). Nawet odpowiedź Mien rozwiązuje ten problem, ograniczając liczbę prawidłowych danych wejściowych do opcji zdefiniowanych przez LogCategoryklasę.
Pharap

30

Możesz dodać atrybuty do elementów w wyliczeniu, a następnie użyć refleksji, aby uzyskać wartości z atrybutów.

Aby zastosować atrybuty, należy użyć specyfikatora „field”:

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

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

Następnie zastanowisz się nad polami statycznymi typu wyliczenia (w tym przypadku GroupTypes) i uzyskasz DescriptionAttributewartość, której szukasz za pomocą odbicia:

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

Zdecydowałem się zwrócić DescriptionAttributesam powyżej, w przypadku, gdy chcesz mieć możliwość ustalenia, czy atrybut jest w ogóle stosowany.


Chociaż zapamiętam to dla bardziej złożonych sytuacji, jest to raczej skomplikowane dla sytuacji o poziomie złożoności tego, co powiedziałem w OP
Boris Callens

26

Możesz to zrobić bardzo łatwo. Użyj następującego kodu.

enum GroupTypes
{
   OEM,
   CMB
};

Następnie, jeśli chcesz uzyskać wartość ciągu każdego elementu wyliczeniowego, skorzystaj z następującego wiersza kodu.

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

W przeszłości z powodzeniem korzystałem z tej metody, a także stosowałem klasę stałych do przechowywania stałych łańcuchowych, obie działają całkiem dobrze, ale zazwyczaj wolę to.


Myślałem o tym samym, ale musi być w tym trochę haczyka ... W przeciwnym razie podejrzewałbym, że więcej osób by to sugerowało (może jestem po prostu paranoikiem).
Matthijs Wessels

Jedyny haczyk, jaki zdaję sobie z tego sprawę, to to, że sądzę, że używa refleksji, aby obliczyć sznur. W rezultacie, jeśli szukam rozwiązania pozwalającego na śledzenie ciągów, to zwykle używam klasy do przechowywania większości ciągów. Jednakże, jeśli mam sytuację, w której Enum jest właściwym rozwiązaniem (niezależnie od uzyskania opisowego ciągu o moich elementach Enum), to zamiast dodatkowego łańcucha pływającego gdzieś do zarządzania, po prostu używam wartości wyliczenia zgodnie z opisem.
Arthur C

+1 To jest najlepsza i najłatwiejsza odpowiedź, a także ma tutaj wysokie głosy , aby to udowodnić. Jedynym momentem, w którym lepiej jest użyć modelu rozszerzenia, jest to, kiedy potrzebujesz spacji w tekście (więcej szczegółów tutaj ).
SharpC

14
Nie, to tylko uzyskanie nazwy wartości wyliczeniowej, a nie przypisanie łańcucha do wartości wyliczeniowej. Celem PO jest, aby ciąg znaków był inny niż wartość wyliczeniowa, np .: TheGroup = „OEM”, TheOtherGroup = „CMB”.
Tim Autin

3
Zgadzam się z komentarzem @ Tima, to nie jest to, co OP próbuje zrobić. Jeśli zastanawiasz się, co to jest przypadek użycia, zastanów się nad sytuacją, w której urządzenie przyjmuje ciągi jako polecenia, ale musi istnieć również wersja polecenia „czytelna dla człowieka”. Potrzebowałem tego, aby powiązać coś w rodzaju „Aktualizuj oprogramowanie układowe” z poleceniem „UPDATEFW”.
JYelton

20

Spróbuj dodać stałe do klasy statycznej. Nie skończysz z typem, ale będziesz mieć czytelne, uporządkowane stałe:

public static class GroupTypes {

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

}

3
Trudno przejść z kodu z powrotem do opisowej nazwy. Aby znaleźć dopasowanie, musisz użyć refleksji nad wszystkimi polami const.
andleer

1
@andleer Nie rozumiem twojej obawy. To jest rozwiązanie, którego używam.
VSO

Tak, właśnie tego chciałem. I to jest najbardziej zwięzłe / eleganckie rozwiązanie, jakie widzę, tak jakbym definiował wartości wyliczenia w / int - ale zamiast nich z wartościami łańcuchowymi. 100% idealny.
Czad

3
Problem polega na tym, że nie działa on jako Enum w tym sensie, że nie będziemy mieli osobnego typu ze skończoną listą wartości. Funkcja oczekująca ich może być używana z ciągami swobodnymi, które są podatne na błędy.
Juan Martinez

14

Utwórz drugie wyliczenie dla swojej bazy danych zawierające następujące elementy:

enum DBGroupTypes
{
    OEM = 0,
    CMB = 1
}

Teraz możesz użyć Enum.Parse, aby pobrać poprawną wartość DBGroupTypes z ciągów „OEM” i „CMB”. Następnie możesz przekonwertować je na int i pobrać poprawne wartości z właściwego wyliczenia, którego chcesz dalej używać w swoim modelu.


To wydaje się być dodatkowym krokiem w procesie, dlaczego nie jedna klasa, która zajmuje się wszystkim?
C. Ross

11
W przeciwieństwie do używania atrybutów i refleksji?
Dave Van den Eynde

13

Użyj klasy.

Edycja: lepszy przykład

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
Trudno przejść z kodu z powrotem do opisowej nazwy. Aby znaleźć dopasowanie, musisz użyć refleksji nad wszystkimi polami const.
andleer

1
Rozumiem co masz na myśli. Prześlę wersję, która działa ostro później, ale przyznaję, że jest dość ciężka.
C. Ross

Moja wersja oparta na rozwiązaniu C. Ross stackoverflow.com/a/48441114/3862615
Roman M

7

Innym sposobem radzenia sobie z tym problemem jest posiadanie wyliczenia i tablicy ciągów, które będą mapować wartości wyliczenia z listą ciągów:

public enum GroupTypes
{
    TheGroup  = 0,
    TheOtherGroup 
}

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

możesz użyć czegoś takiego:

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

Poprosi CMB

Plusy:

  1. Łatwy i czysty kod.
  2. Wysoka wydajność (szczególnie w porównaniu z podejściami wykorzystującymi klasy)

CONS:

  1. Skłonność do zepsucia listy podczas edytowania, ale w przypadku krótkiej listy będzie w porządku.

6

Oto metoda rozszerzenia, której użyłem, aby uzyskać wartość wyliczenia jako ciąg. Najpierw tutaj jest wyliczenie.

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

Atrybut Opis pochodzi z System.ComponentModel.

A oto moja metoda rozszerzenia:

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

Teraz możesz uzyskać dostęp do wyliczenia jako wartości ciągu za pomocą następującego kodu:

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

5

Czy zastanawiałeś się nad tabelą odnośników za pomocą słownika?

enum GroupTypes
{
    TheGroup,
    TheOtherGroup
}

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

Następnie możesz użyć GroupTypeLookup.TryGetValue (), aby wyszukać ciąg znaków podczas jego czytania.


Jak łatwo zdobyć klucz dla danej wartości?
eglasius

Pytanie nie wymagało pójścia w drugą stronę. Ale zbudowanie kolejnego słownika działającego w drugą stronę byłoby wystarczająco proste. Oznacza to, że 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 # nie obsługuje wyliczonych ciągów, ale w większości sytuacji można użyć listy lub słownika, aby uzyskać pożądany efekt.

Np. Aby wydrukować wyniki pozytywny / negatywny:

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

2

Zrobiłbym z tego klasę, żeby całkowicie uniknąć wyliczenia. A następnie za pomocą modułu obsługi typów można utworzyć obiekt, gdy pobierzesz go z bazy danych.

TO ZNACZY:

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

Po prostu stworzę słownik i użyję kodu jako klucza.

Edycja: Aby odpowiedzieć na komentarz dotyczący wyszukiwania wstecznego (znajdowanie klucza), nie byłoby to zbyt wydajne. Jeśli to konieczne, napisałbym nową klasę do obsługi.


Czy możesz też łatwo zdobyć klucz dla danej wartości?
eglasius

Do C.Ross - nie jestem pewien, co masz na myśli. Możesz odczytać wartości z bazy danych i dynamicznie zapełniać słownik.
jhale

2

Moje pierwsze pytanie - czy masz dostęp do samej bazy danych? Powinno to zostać znormalizowane w bazie danych, idealnie, w przeciwnym razie każde rozwiązanie będzie podatne na błędy. Z mojego doświadczenia wynika, że ​​pola danych pełne „OEM” i „CMB” zwykle kończą się mieszaniem takich rzeczy jak „oem” i inne „badziewne dane” z czasem… Jeśli możesz to znormalizować, możesz użyć klucza w tabeli zawierającej elementy jako twoje Enum i gotowe - z dużo czystszą strukturą.

Jeśli to nie jest dostępne, stworzę twoje Enum i stworzę dla ciebie klasę, która parsuje twój łańcuch znaków w Enum. Zapewniłoby to co najmniej pewną elastyczność w obsłudze niestandardowych wpisów i znacznie większą elastyczność w zakresie wychwytywania lub obsługi błędów niż wykonywanie jakichkolwiek obejść za pomocą Enum.Parse / Reflection / itp. Słownik będzie działał, ale może się zepsuć, jeśli będziesz mieć problemy ze sprawami itp.

Polecam napisanie zajęć, abyś mógł:

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

Pozwala to zachować większość czytelności bez konieczności zmiany DB.


2

Jeśli dobrze rozumiem, potrzebujesz konwersji z łańcucha na wyliczenie:

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

Możesz zrobić to bardziej fantazyjnie z rodzajami dla typu wyliczeniowego, jeśli chcesz.


2

W VS 2015 możesz użyć nameof

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

Stosowanie:

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

2

Mała poprawka do metody Glennular Extension, abyś mógł używać rozszerzenia do innych celów niż tylko ENUM;

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

Lub używając 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

Po odpowiedzi na @Even Mien próbowałem pójść nieco dalej i uczynić go Ogólnym, wydaje mi się, że już prawie tam jestem, ale jedna sprawa wciąż się opiera i prawdopodobnie mogę trochę uprościć mój kod.
Publikuję go tutaj, jeśli ktoś zobaczy, jak mogę ulepszyć, a zwłaszcza sprawi, że będzie działał, ponieważ nie mogę przypisać go z ciągu

Do tej pory mam następujące wyniki:

        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 .... :(:(

Gdzie dzieje się magia:

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



}

Dopiero wtedy muszę to zadeklarować dla moich wyliczeń:

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

Dziękuję za tę świetną pracę, długo szukałem takiego podejścia. Myślę, że powinieneś zdobyć za to 1000 punktów
użytkownik3492977

Och, dziękuję za ten komentarz i dziękuję za przypomnienie mi o tym, że nie używałem c # przez dwa lata, kiedy napisałem ten kawałek kodu, powinienem do niego wkrótce wrócić!
Lomithrani

@ user3492977 W końcu wróciłem do tego i sprawiłem, że stał się w pełni funkcjonalny, wciąż jednak mam wątpliwości, czy to świetny pomysł lub bezużyteczna rzecz: D stackoverflow.com/questions/62043138/...
Lomithrani

2

Nowością w .Net Core 3.0 / C # 8.0 (jeśli twoje środowisko pracy pozwala na aktualizację projektu) jest krótka instrukcja przełącznika, która wygląda nieco enum. Pod koniec dnia jest to ta sama stara nudna deklaracja zmiany, której używamy od lat.

Jedyną prawdziwą różnicą jest to, że instrukcja zmiany ma nowy kolor.

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

Zauważysz, że kod powyżej, który skopiowałem stąd , faktycznie używa enum jako parametru.

Nie jest to dokładnie to, czego chcesz (i zaufaj mi, chciałem czegoś podobnego do tego, o co OP prosi od dawna), ale tak naprawdę czuję, że jest to coś w rodzaju gałązki oliwnej z MS. JMO.

Mam nadzieję, że to komuś pomaga!


2

Użyłem struktury, jak wspomniano w poprzedniej odpowiedzi, ale pozbyłem się jakiejkolwiek złożoności. Dla mnie było to jak tworzenie wyliczenia ciągów. Jest używany w taki sam sposób, jak używane jest wyliczenie.

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

Przykładowe zastosowanie:

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

1

Zaimplementowałem nawet kilka wyliczeń, jak sugeruje @Even (via class Xi public static Xczłonkowie), aby dowiedzieć się później, że w dzisiejszych czasach, począwszy od .Net 4.5, jest właściwe ToString() metoda.

Teraz przywracam wszystko z powrotem do wyliczeń.


1

Jest to sposób na użycie go jako parametru o silnym typie lub ciągu :

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

Możesz użyć dwóch wyliczeń. Jeden dla bazy danych, a drugi dla czytelności.

Musisz tylko upewnić się, że pozostają zsynchronizowane, co wydaje się niewielkim kosztem. Nie musisz ustawiać wartości, po prostu ustaw pozycje w ten sam sposób, ale ustawienie wartości wyraźnie pokazuje, że oba wyliczenia są powiązane i zapobiega błędom przestawiania elementów wyliczenia. A komentarz informuje ekipę serwisową, że są one powiązane i muszą być zsynchronizowane.

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

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

Aby go użyć, najpierw przekonwertuj na kod:

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

Następnie, jeśli chcesz uczynić to jeszcze wygodniejszym, możesz dodać funkcję rozszerzenia, która działa tylko dla tego rodzaju wyliczenia:

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

i wtedy możesz po prostu zrobić:

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

Jest to zła praktyka: w przypadku zależnej enumzamierzona zmiana wartości w jednym może nieumyślnie zepsuć drugą.
Lorenz Lo Sauer

1

Zasadniczo szukałem odpowiedzi Reflection @ArthurC

Aby nieco rozszerzyć jego odpowiedź, możesz uczynić ją jeszcze lepszą, mając ogólną funkcję:

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

Następnie możesz po prostu owinąć wszystko, co masz

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

lub

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

1

Zaczerpnięte z @EvenMien i dodane w niektórych komentarzach. (Również dla mojego własnego przypadku użycia)

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

Dodanie tej klasy

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

Ta praca używa CallerMemberName do zminimalizowania kodowania

Za pomocą:

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

Sprawdź to:

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

wynik:

ScannerDefaultFlashLight
Scanner1dCodes

0

Opierając się na innych opiniach, właśnie o tym myślę. Takie podejście pozwala uniknąć wpisywania wartości .Value w miejscu, w którym chcesz uzyskać stałą wartość.

Mam klasę podstawową dla wszystkich wyliczeń ciągów takich jak to:

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
}

JsonConverter wygląda następująco:

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

A rzeczywiste wyliczenie struny będzie takie:

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

Dzięki temu możesz po prostu użyć Color.Red, aby nawet serializować do formatu json bez korzystania z właściwości Value


0

Nie potrzebowałem niczego solidnego, jak przechowywanie łańcucha w atrybutach. Musiałem tylko zmienić coś MyEnum.BillEveryWeekw „rachunek co tydzień” lub MyEnum.UseLegacySystem„używać starszego systemu” - w zasadzie podzielić wyliczenie przez obudowę wielbłąda na pojedyncze małe litery.

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() wyjścia „użyj starszego systemu”

Jeśli masz ustawione wiele flag, zmieni to w zwykły angielski (rozdzielany przecinkami, z wyjątkiem „i” zamiast ostatniego przecinka).

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

Console.WriteLine(myCustomerBehaviour.UnCamelCase());
//outputs "bill every week, use legacy system and charge taxes"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.