Ciąg reprezentujący wyliczenie


912

Mam następujące wyliczenie:

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

Problemem jest jednak to, że potrzebuję słowa „FORMULARZE”, gdy pytam o AuthenticationMethod.FORMS, a nie identyfikator 1.

Znalazłem następujące rozwiązanie tego problemu ( link ):

Najpierw muszę utworzyć niestandardowy atrybut o nazwie „StringValue”:

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

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

    public string Value
    {
        get { return _value; }
    }

}

Następnie mogę dodać ten atrybut do mojego modułu wyliczającego:

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

I oczywiście potrzebuję czegoś, aby odzyskać StringValue:

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

Dobrze, że teraz mam narzędzia do uzyskania wartości ciągu dla modułu wyliczającego. Następnie mogę użyć tego w następujący sposób:

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

Dobra, teraz wszystkie z nich działają jak urok, ale uważam, że to dużo pracy. Zastanawiałem się, czy istnieje na to lepsze rozwiązanie.

Próbowałem też czegoś ze słownikiem i właściwościami statycznymi, ale to też nie było lepsze.


8
Miły! Mogę tego użyć do przetłumaczenia wartości wyliczeniowych na zlokalizowane ciągi.
Øyvind Skaar

5
Choć może się okazać, że jest to dość skomplikowane, w rzeczywistości jest to dość elastyczny sposób na inne rzeczy. Jak zauważył jeden z moich kolegów, można to w wielu przypadkach wykorzystać do zastąpienia Enum Helpers, które
mapują

27
MSDN zaleca klasy atrybutów z sufiksem „Atrybut”. Tak więc „class StringValueAttribute”;)
serhio

14
Zgadzam się z @BenAlabaster, jest to właściwie dość elastyczne. Możesz także uczynić to rozszerzeniem tylko poprzez dodanie thisprzed Enummetodą statyczną. Potem możesz zrobić AuthenticationMethod.Forms.GetStringValue();
Justin Pihony

5
Podejście to wykorzystuje refleksję do odczytu wartości atrybutów i jest bardzo powolne, jeśli z mojego doświadczenia trzeba wywoływać GetStringValue () wiele razy. Wzór typu bezpiecznego wyliczenia jest szybszy.
Rn222

Odpowiedzi:


868

Wypróbuj wzór typu bezpieczny enum .

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

}

Zaktualizuj Konwersję typu Jawnego (lub niejawnego) można wykonać za pomocą

  • dodanie pola statycznego z mapowaniem

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • nb Aby inicjalizacja pól „elementu enum” nie powodowała wygenerowania wyjątku NullReferenceException podczas wywoływania konstruktora instancji, pamiętaj o umieszczeniu pola Dictionary przed polami „enum member” w klasie. Wynika to z tego, że inicjatory pola statycznego są wywoływane w kolejności deklaracji, a przed konstruktorem statycznym, tworząc dziwną i konieczną, ale mylącą sytuację, można wywołać konstruktor instancji przed zainicjowaniem wszystkich pól statycznych i przed wywołaniem konstruktora statycznego.
  • wypełnianie tego mapowania w konstruktorze instancji

    instance[name] = this;
  • i dodanie operatora konwersji typu zdefiniowanego przez użytkownika

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }

17
Wygląda jak wyliczenie, ale nie jest wyliczeniem. Mogę sobie wyobrazić, że powoduje to kilka interesujących problemów, jeśli ludzie zaczną próbować porównywać metody uwierzytelniania. Prawdopodobnie będziesz też musiał przeciążać różne operatory równości.
Ant

36
@Ant: Nie muszę. Ponieważ mamy tylko jedną instancję każdej metody uwierzytelnienia, równość referencji odziedziczona z obiektu działa poprawnie.
Jakub Šturc

10
@tyriker: Kompilator działa. Konstruktor jest prywatny, więc nie można utworzyć nowej instancji. Również elementy statyczne nie są dostępne poprzez instancję.
Jakub Šturc

21
@Jakub Bardzo interesujące. Musiałem się nim bawić, żeby dowiedzieć się, jak go używać i uświadomić sobie jego zalety. Jest to publiczna, niestatyczna klasa, ale nie można jej tworzyć i można uzyskać dostęp tylko do jej statycznych elementów. Zasadniczo zachowuje się jak wyliczenie. Ale najlepsza część ... elementy statyczne są typami klasy, a nie ciągiem ogólnym ani int. To jest ... [poczekaj na to] ... typ bezpiecznego enum! Dzięki za pomoc w zrozumieniu.
tyriker

6
@kiran Poniżej zamieściłem nieco zmodyfikowaną wersję odpowiedzi Jakuba Šturca, która pozwala na użycie jej z instrukcjami Switch-Case, więc teraz nie ma wady tego podejścia :)
deadlydog

228

Użyj metody

Enum.GetName(Type MyEnumType,  object enumvariable)  

jak w (Załóżmy, że ShipperEnum jest zdefiniowane)

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

Istnieje również szereg innych metod statycznych w klasie Enum, które warto zbadać ...


5
Dokładnie. Zrobiłem niestandardowy atrybut opisu ciągu, ale to dlatego, że chcę wersję przyjazną dla użytkownika (ze spacjami i innymi znakami specjalnymi), którą można łatwo powiązać z ComboBox lub podobnym.
lc.

5
Enum.GetName odzwierciedla nazwy pól w wyliczeniu - tak samo jak .ToString (). Jeśli wydajność jest problemem, może to być problem. Nie martwiłbym się tym, dopóki nie przerobisz mnóstwa wyliczeń.
Keith

8
Inną opcją do rozważenia, jeśli potrzebujesz wyliczenia o dodatkowej funkcjonalności, jest „roll yr own” przy użyciu struktury ... dodajesz statyczne właściwości o nazwach tylko do odczytu, aby reprezentować wartości wyliczania inicjowane w konstruktorach generujących poszczególne wystąpienia struktury ...
Charles Bretana,

1
wtedy możesz dodać dowolnych innych członków struktury, których chcesz, aby zaimplementować dowolną funkcjonalność, jaką ma mieć to „wyliczenie” ...
Charles Bretana

2
Problem polega na tym, że nie można zlokalizować GetName. Nie zawsze jest to problem, ale należy o tym pamiętać.
Joel Coehoorn

79

Możesz odwoływać się do nazwy zamiast wartości, używając ToString ()

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

Dokumentacja jest tutaj:

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

... a jeśli nazwiesz swoje wyliczenia w Pascal Case (tak jak ja - na przykład ThisIsMyEnumValue = 1 itd.), możesz użyć bardzo prostego wyrażenia regularnego do wydrukowania przyjaznego formularza:

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

które można łatwo wywołać z dowolnego ciągu:

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

Wyjścia:

Konwertuj mój szalony przypadek Pascala na przyjazny przypadek

Oszczędza to biegania po domach, tworzenia niestandardowych atrybutów i dołączania ich do wyliczeń lub używania tabel odnośników do łączenia wartości wyliczenia z przyjaznym ciągiem znaków, a co najważniejsze, sam się zarządza i można go stosować na dowolnym ciągu sprawy Pascala, który jest nieskończony bardziej wielokrotnego użytku. Oczywiście nie pozwala ci to mieć innej przyjaznej nazwy niż twoje wyliczenie, które zapewnia twoje rozwiązanie.

Podoba mi się jednak twoje oryginalne rozwiązanie w przypadku bardziej złożonych scenariuszy. Możesz pójść o krok dalej i uczynić GetStringValue metodą rozszerzenia Twojego wyliczenia, a wtedy nie będziesz musiał odwoływać się do niego jak StringEnum.GetStringValue ...

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

Możesz wtedy uzyskać do niego łatwy dostęp bezpośrednio z instancji enum:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());

2
To nie pomaga, jeśli „przyjazna nazwa” potrzebuje spacji. Takich jak „Forms Authentication”
Ray Booysen

4
Upewnij się więc, że wyliczenie jest nazwane za pomocą wielkich liter, takich jak FormsAuthentication, i wstaw spację przed wszystkimi dużymi literami, które nie są na początku. Umieszczanie spacji w sznurku nie jest nauką rakietową ...
BenAlabaster

4
Automatyczne odstępy między nazwami liter Pascala stają się problematyczne, jeśli zawierają skróty, które powinny być pisane dużymi literami, na przykład XML lub GPS.
Richard Ev

2
@RichardEv, nie ma do tego idealnego wyrażenia regularnego, ale oto taki, który powinien działać nieco lepiej ze skrótami. "(?!^)([^A-Z])([A-Z])", "$1 $2". Tak się HereIsATESTstaje Here Is ATEST.
sparebytes

Nie jest eleganckie robienie tych małych „hacków”, jakie są. Rozumiem, co mówi OP i próbuję znaleźć podobne rozwiązanie, tj. Używając elegancji Enums, ale mając łatwy dostęp do powiązanej wiadomości. Jedynym rozwiązaniem, jakie mogę wymyślić, jest zastosowanie pewnego rodzaju mapowania między nazwą wyliczenia a wartością ciągu, ale nie wiąże się to z utrzymywaniem danych ciągu (jednak czyni to praktycznym w scenariuszach, w których musisz mieć wiele regionów itp. )
Tahir Khalid

72

Niestety refleksja w celu uzyskania atrybutów na wyliczeniach jest dość powolna:

Zobacz to pytanie: Czy ktoś zna szybki sposób na uzyskanie niestandardowych atrybutów dla wartości wyliczeniowej?

.ToString()Jest dość powolna na teksty stałe też.

Możesz jednak pisać metody rozszerzeń dla wyliczeń:

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

To nie jest świetne, ale będzie szybkie i nie wymaga refleksji dla atrybutów lub nazwy pola.


Aktualizacja C # 6

Jeśli możesz użyć C # 6, nowy nameofoperator działa dla wyliczeń, więc nameof(MyEnum.WINDOWSAUTHENTICATION)zostanie przekonwertowany "WINDOWSAUTHENTICATION"na czas kompilacji , dzięki czemu będzie to najszybszy sposób na uzyskanie nazw wyliczeń.

Zauważ, że to przekształci jawny wyliczenie w wstawioną stałą, więc nie działa dla wyliczeń, które masz w zmiennej. Więc:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Ale...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"

24
Możesz pobrać wartości atrybutów jeden raz i umieścić je w Słowniku <MyEnum, string>, aby zachować aspekt deklaratywny.
Jon Skeet

1
Tak właśnie skończyliśmy w aplikacji z dużą ilością wyliczeń, gdy dowiedzieliśmy się, że odbiciem jest szyjka butelki.
Keith

Dzięki Jon i Keith, skończyłem z twoją sugestią ze słownika. Działa świetnie (i szybko!).
Helge Klein

@JonSkeet Wiem, że to jest stare. Ale jak to osiągnąć?
user919426

2
@ user919426: Osiągnąć chcesz? Umieszczasz je w słowniku? Wystarczy utworzyć słownik, najlepiej z inicjatorem kolekcji ... nie jest jasne, o co prosisz.
Jon Skeet,

59

Używam metody rozszerzenia:

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

Teraz udekoruj za enumpomocą:

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

Kiedy zadzwonisz

AuthenticationMethod.FORMS.ToDescription()dostaniesz "FORMS".


1
Musiałem using System.ComponentModel;także dodać , ta metoda działa tylko wtedy, gdy chcesz, aby wartość ciągu była taka sama jak nazwa Enum. OP chciał innej wartości.
elcool,

2
Nie masz na myśli kiedy dzwonisz AuthenticationMethod.FORMS.ToDescription()?
nicodemus13

41

Po prostu użyj ToString()metody

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

Aby odwołać się do ciągu Tomato, po prostu użyj

any.Tomato.ToString();

Łał. To było łatwe. Wiem, że OP chciał dodać niestandardowe opisy ciągów, ale tego właśnie potrzebowałem. Powinienem był wiedzieć, że mogę tego spróbować z perspektywy czasu, ale poszedłem drogą Enum.GetName.
Rafe,

7
Dlaczego wszyscy to komplikują?
Brent

18
@Brent Ponieważ najczęściej masz .ToString()wartość inną niż wartość przyjazna dla użytkownika, której potrzebujesz.
Novitchi S,

2
@Brent - ponieważ różni się to od zadawanego pytania. Zadawane jest pytanie, w jaki sposób uzyskać ten ciąg ze zmiennej, której przypisano wartość wyliczoną. To jest dynamiczne w czasie wykonywania. Sprawdza to definicję typu i ustawia w czasie wykonywania.
Hogan,

1
@Hogan - ToString () działa również na zmienne: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV

29

Bardzo proste rozwiązanie tego problemu. Net 4.0 lub nowszy. Nie jest potrzebny żaden inny kod.

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

Aby uzyskać ciąg o po prostu użyj:

MyStatus.Active.ToString("f");

lub

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

Wartość będzie „Aktywna” lub „Zarchiwizowana”.

Aby zobaczyć różne formaty ciągów („f” z góry) podczas wywoływania, Enum.ToStringzobacz stronę Ciągi formatowania wyliczeń


28

Korzystam z atrybutu Opis z przestrzeni nazw System.ComponentModel. Po prostu udekoruj wyliczenie, a następnie użyj tego kodu, aby go odzyskać:

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

        }

Jako przykład:

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

Ten kod ładnie obsługuje wyliczenia, w których nie potrzebujesz „przyjaznej nazwy”, i zwróci tylko .ToString () z wyliczenia.


27

Naprawdę podoba mi się odpowiedź Jakuba Šturca, ale jej wadą jest to, że nie można jej użyć z instrukcją przełączania. Oto nieco zmodyfikowana wersja jego odpowiedzi, której można użyć z instrukcją switch:

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

Dzięki temu zyskujesz wszystkie korzyści z odpowiedzi Jakuba Šturca, a ponadto możemy go użyć z instrukcją switch w następujący sposób:

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

Krótszym rozwiązaniem byłoby usunięcie wyliczeń {} i zamiast tego utrzymywanie statycznej liczby wytworzonych wyliczeń. Daje to również tę korzyść, że nie musisz dodawać nowego wystąpienia do listy wyliczeń. np. public static int nextAvailable { get; private set; }następnie w konstruktorzethis.Value = nextAvailable++;
kjhf

Ciekawy pomysł @kjhf. Obawiam się jednak, że jeśli ktoś zmieni kolejność kodu, wówczas wartość przypisana do wartości wyliczeniowych może również ulec zmianie. Na przykład może to spowodować pobranie niewłaściwej wartości wyliczenia, gdy wartość wyliczenia zostanie zapisana w pliku / bazie danych, kolejność wierszy „nowa metoda uwierzytelniania (...)” zostanie zmieniona (np. Jedna zostanie usunięta), a następnie ponowne uruchomienie aplikacji i pobieranie wartości wyliczenia z pliku / bazy danych; wartość wyliczona może nie odpowiadać pierwotnie zapisanej metodzie uwierzytelniania.
deadlydog

Dobra uwaga - chociaż mam nadzieję, że w tych szczególnych przypadkach ludzie nie będą polegać na wartości całkowitej wyliczenia (lub zmianie kolejności kodu wyliczenia.) - i ta wartość jest używana wyłącznie jako przełącznik i być może alternatywa dla .Equals () i. GetHashCode (). W razie wątpliwości można zawsze umieścić ogromny komentarz z tekstem „NIE PRZESYŁAJ”: p
kjhf

Czy nie możesz po prostu przeciążyć =operatora, aby umożliwić przełączenie do pracy? Zrobiłem to w VB i mogę teraz używać tego w select casezestawieniu.
user1318499,

@ user1318499 Nie, C # ma bardziej rygorystyczne reguły wokół instrukcji switch niż VB. Nie można używać instancji klas do instrukcji Case; możesz używać tylko stałych prymitywów.
deadlydog,

13

Używam kombinacji kilku powyższych sugestii w połączeniu z pewnym buforowaniem. Teraz wpadłem na pomysł z jakiegoś kodu, który znalazłem gdzieś w sieci, ale nie pamiętam, skąd go wziąłem ani go nie znalazłem. Jeśli więc ktoś znajdzie coś, co wygląda podobnie, prosimy o komentarz z tym opisem.

W każdym razie użycie obejmuje konwertery typów, więc jeśli jesteś powiązany z interfejsem użytkownika, „po prostu działa”. Wzorzec Jakuba można rozszerzyć w celu szybkiego wyszukiwania kodu, inicjując z konwertera typów na metody statyczne.

Podstawowe użycie wyglądałoby tak

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

Kod niestandardowego konwertera typu wyliczeniowego wygląda następująco:

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

}


12

W swoim pytaniu nigdy nie powiedziałeś, że naprawdę potrzebujesz wszędzie wartości liczbowej wyliczenia.

Jeśli nie potrzebujesz i potrzebujesz tylko wyliczenia typu string (który nie jest typem integralnym, więc nie może być podstawą wyliczenia), oto sposób:

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

możesz użyć tej samej składni, co enum, aby się do niego odwoływać

if (bla == AuthenticationMethod.FORMS)

Będzie on nieco wolniejszy niż w przypadku wartości liczbowych (porównywanie ciągów zamiast liczb), ale po stronie plus nie używa odbicia (powolnego) w celu uzyskania dostępu do ciągu.


jeśli użyjesz „const” zamiast „static readonly”, możesz użyć wartości jako etykiet wielkości liter w instrukcji switch.
Ed N.

11

Jak rozwiązałem to jako metodę rozszerzenia:

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

Enum:

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

Użycie (gdzie o.OrderType jest właściwością o tej samej nazwie co wyliczenie):

o.OrderType.GetDescription()

Co daje mi ciąg „Nowa karta” lub „Przeładuj” zamiast rzeczywistej wartości wyliczeniowej NewCard i napełniania.


Dla kompletności powinieneś dołączyć kopię swojej klasy DescriptionAttribute.
Bernie White,

3
Bernie, DescriptionAttribute znajduje się w System.ComponentModel
agentnega

11

Aktualizacja: Odwiedzając tę ​​stronę, 8 lat później, po dłuższym nie dotykaniu C #, wygląda na to, że moja odpowiedź nie jest już najlepszym rozwiązaniem. Naprawdę podoba mi się rozwiązanie konwertera związane z funkcjami atrybutów.

Jeśli to czytasz, sprawdź także inne odpowiedzi.
(wskazówka: są powyżej tego)


Jak większość z was bardzo podobała mi się wybrana odpowiedź Jakuba Šturca , ale też bardzo nie lubię kopiować i wklejać kodu i staram się robić to tak mało, jak potrafię.

Zdecydowałem więc, że chcę klasy EnumBase, z której większość funkcji jest dziedziczona / wbudowana, co pozwala mi skupić się na treści zamiast na zachowaniu.

Główny problem z tym podejściem polega na tym, że chociaż wartości Enum są instancjami bezpiecznymi dla typu, interakcja dotyczy statycznej implementacji typu Enum Class. Więc przy odrobinie magii generycznej myślę, że w końcu udało mi się uzyskać właściwy miks. Mam nadzieję, że ktoś uzna to za tak przydatne, jak ja.

Zacznę od przykładu Jakuba, ale używając dziedziczenia i ogólnych:

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

A oto klasa podstawowa:

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
}

Możesz być w stanie wywołać pochodnego konstruktora statycznego z podstawowego konstruktora statycznego. Nadal się nad tym zastanawiam, ale jak dotąd nie znalazłem z nim żadnych problemów: stackoverflow.com/questions/55290034/...
Cory-G

10

Zgadzam się z Keithem, ale nie mogę (jeszcze) głosować.

Używam metody statycznej i instrukcji swith, aby zwrócić dokładnie to, czego chcę. W bazie danych przechowuję tinyint, a mój kod wykorzystuje tylko rzeczywiste wyliczanie, więc ciągi znaków odpowiadają wymaganiom interfejsu użytkownika. Po licznych testach spowodowało to najlepszą wydajność i największą kontrolę nad wydajnością.

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

Jednak według niektórych kont prowadzi to do możliwego koszmaru konserwacji i zapachu kodu. Staram się obserwować wyliczenia, które są długie i dużo wyliczeń, lub te, które często się zmieniają. W przeciwnym razie było to dla mnie świetne rozwiązanie.


10

Jeśli przyszedłeś tutaj, aby zaimplementować prosty „Enum”, ale którego wartości to ciągi zamiast ints, oto najprostsze rozwiązanie:

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

Realizacja:

var someStringVariable = MetricValueList.Brand;

2
Prawdopodobnie lepiej jest ustawić zmienne consts zamiast ich używać static readonly.
AndyGeek,

1
consts nie są dobre dla publicznie dostępnych klas, ponieważ są one wypiekane w czasie kompilacji, nie można zastąpić biblioteki DLL innej firmy bez ponownej kompilacji całego kodu z const.
Kristian Williams

7

Kiedy napotykam ten problem, na kilka pytań staram się znaleźć odpowiedzi w pierwszej kolejności:

  • Czy nazwy moich wartości wyliczeniowych są wystarczająco przyjazne dla tego celu, czy też muszę podać te bardziej przyjazne?
  • Czy muszę podróżować w obie strony? Czy muszę brać wartości tekstowe i analizować je na wartości wyliczeniowe?
  • Czy to coś, co muszę zrobić dla wielu wyliczeń w moim projekcie, czy tylko jeden?
  • Jakiego rodzaju elementów interfejsu użytkownika przedstawię te informacje - w szczególności, czy będę wiążący dla interfejsu użytkownika lub za pomocą arkuszy właściwości?
  • Czy to musi być zlokalizowane?

Najprostszym sposobem na to jest Enum.GetValue(i wsparcie przy użyciu tripping tripping Enum.Parse). Często warto również zbudować TypeConverter, jak sugeruje Steve Mitcham, obsługę interfejsu użytkownika. (Nie trzeba budowaćTypeConverter gdy używasz arkuszy własności, co jest jedną z miłych rzeczy w arkuszach własności. Chociaż pan wie, że mają swoje własne problemy).

Ogólnie rzecz biorąc, jeśli odpowiedzi na powyższe pytania sugerują, że to nie zadziała, moim następnym krokiem jest stworzenie i wypełnienie pliku statycznego Dictionary<MyEnum, string>lub ewentualnie Dictionary<Type, Dictionary<int, string>>. Często pomijam pośredni krok dekorowania kodu z atrybutami, ponieważ to, co zwykle schodzi po szczupaku, to konieczność zmiany przyjaznych wartości po wdrożeniu (często, ale nie zawsze, z powodu lokalizacji).


7

Chciałem opublikować to jako komentarz do cytowanego poniżej postu, ale nie mogłem, ponieważ nie mam wystarczającej liczby przedstawicieli - więc proszę nie głosować w dół. Kod zawierał błąd i chciałem zwrócić uwagę na osoby próbujące skorzystać z tego rozwiązania:

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

Powinien być

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

Brillant!


5

Mój wariant

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

Kod wygląda trochę brzydko, ale zastosowania tej struktury są dość reprezentatywne.

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

Myślę też, że jeśli wymagane jest wiele takich wyliczeń, można by użyć generowania kodu (np. T4).


4

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

i wtedy

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

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

DoSomethingWithTheAuth(auth.ToString());

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

4

Jeśli pomyślisz o problemie, który próbujemy rozwiązać, w ogóle nie potrzebujemy enum. Potrzebujemy obiektu, który pozwala na powiązanie określonej liczby wartości ze sobą; innymi słowy, aby zdefiniować klasę.

Bezpieczny wzór wyliczenia Jakuba Šturca jest najlepszą opcją, jaką tu widzę.

Spójrz na to:

  • Ma prywatnego konstruktora, więc tylko sama klasa może zdefiniować dozwolone wartości.
  • Jest to klasa zapieczętowana, więc wartości nie można modyfikować przez dziedziczenie.
  • Jest bezpieczny dla typu, dzięki czemu twoje metody mogą wymagać tylko tego typu.
  • Uzyskiwanie dostępu do wartości nie powoduje żadnego pogorszenia wydajności odbicia.
  • Na koniec można go zmodyfikować, aby skojarzyć więcej niż dwa pola razem, na przykład Nazwę, Opis i Wartość liczbową.

4

dla mnie pragmatyczne podejście to klasa wewnątrz klasy, przykład:

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

4

Utworzyłem klasę bazową do tworzenia wyliczeń o wartościach łańcuchowych w .NET. Jest to tylko jeden plik C #, który można skopiować i wkleić do swoich projektów lub zainstalować za pomocą pakietu NuGet o nazwie StringEnum . GitHub Repo

  • Intellisense zasugeruje nazwę wyliczenia, jeśli klasa jest opatrzona adnotacjami z komentarzem xml <completitionlist>. (Działa zarówno w języku C #, jak i VB)

Demo Intellisense

  • Użycie podobne do zwykłego wyliczania:
///<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)

Instalacja:

  • Wklej następującą klasę podstawową StringEnum do swojego projektu. ( najnowsza wersja )
  • Lub zainstaluj pakiet StringEnum NuGet, który jest oparty na, .Net Standard 1.0więc działa na .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6 itd.
    /// <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/
            }
        }
    }

3

Oto jeszcze jeden sposób na wykonanie zadania powiązania ciągów z wyliczeniami:

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

Ta metoda nazywa się tak:

public FormMain() {
    DATABASE dbEnum;

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

Możesz grupować powiązane wyliczenia w ich własnej strukturze. Ponieważ ta metoda wykorzystuje typ wyliczenia, można użyć Intellisense do wyświetlenia listy wyliczeń podczas tworzeniaGetString() połączenia.

Opcjonalnie możesz użyć nowego operatora w DATABASEstrukturze. Nieużywanie go oznacza, że ​​łańcuchy Listnie są przydzielane, dopóki nie GetString()zostanie wykonane pierwsze wywołanie.


3

Wiele świetnych odpowiedzi tutaj, ale w moim przypadku nie rozwiązałem tego, co chciałem z „enum strun”, czyli:

  1. Do użycia w instrukcji switch, np. Switch (myEnum)
  2. Może być stosowany w parametrach funkcji, np. Foo (typ myEnum)
  3. Można się do niego odwołać np. MyEnum.FirstElement
  4. Mogę używać ciągów znaków, np. Foo („FirstElement”) == foo (myEnum.FirstElement)

1,2 i 4 można faktycznie rozwiązać za pomocą C # Typedef ciągu (ponieważ ciągi można przełączać w c #)

3 można rozwiązać za pomocą stałych statycznych. Więc jeśli masz te same potrzeby, jest to najprostsze podejście:

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

}

Pozwala to na przykład:

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

i

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

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Gdzie CreateType można wywołać za pomocą łańcucha lub typu. Jednak wadą jest to, że każdy ciąg znaków jest automatycznie prawidłowym wyliczeniem , można to zmodyfikować, ale wtedy wymagałoby to jakiejś funkcji init ... a może sprawi, że będą one jawnie rzutowane wewnętrznie?

Teraz, jeśli wartość int było dla ciebie ważne (być może dla porównania prędkości), to mogłaby korzystać z niektórych pomysłów z Jakub Šturc fantastycznej odpowiedzi i zrobić coś bit szalony, to moja stab na to:

    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

}

ale oczywiście „Typ bob = 4;” byłoby bez znaczenia, gdybyś ich nie zainicjował, co w pewnym sensie doprowadziłoby do porażki ...

Ale teoretycznie TypeA == TypeB byłby szybszy ...


3

Jeśli rozumiem cię poprawnie, możesz po prostu użyć .ToString (), aby pobrać nazwę wyliczenia z wartości (Zakładając, że jest już rzutowany jako wyliczenie); Jeśli miałeś nagą int (powiedzmy z bazy danych lub czegoś), możesz najpierw rzucić ją na wyliczenie. Obie metody poniżej pozwolą ci uzyskać nazwę enum.

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

Pamiętaj jednak, że druga technika zakłada, że ​​używasz ints, a twój indeks jest oparty na 1 (a nie na 0). Dla porównania, funkcja GetNames jest również dość ciężka, generujesz całą tablicę za każdym razem, gdy jest wywoływana. Jak widać w pierwszej technice, funkcja .ToString () jest w rzeczywistości wywoływana niejawnie. Oba są już wspomniane w odpowiedziach, staram się tylko wyjaśnić różnice między nimi.


3

stary post, ale ...

Odpowiedź na to może być bardzo prosta. Użyj funkcji Enum.ToString ()

Istnieje 6 przeciążeń tej funkcji, możesz użyć Enum.Tostring („F”) lub Enum.ToString (), aby zwrócić wartość ciągu. Nie musisz zawracać sobie głowy niczym innym. Tutaj jest działająca wersja demonstracyjna

Pamiętaj, że to rozwiązanie może nie działać dla wszystkich kompilatorów ( ta wersja demonstracyjna nie działa zgodnie z oczekiwaniami ), ale przynajmniej działa dla najnowszego kompilatora.



2

Cóż, po przeczytaniu wszystkich powyższych stwierdzeń wydaje mi się, że faceci nadmiernie skomplikowali kwestię przekształcania enumeratorów w łańcuchy. Podobał mi się pomysł posiadania atrybutów nad polami wyliczanymi, ale myślę, że atrybuty są używane głównie do metadanych, ale w twoim przypadku myślę, że wszystko, czego potrzebujesz, to jakaś lokalizacja.

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

Teraz, jeśli spróbujemy wywołać powyższą metodę, możemy to nazwać w ten sposób

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

Wszystko, co musisz zrobić, to po prostu utworzyć plik zasobów zawierający wszystkie wartości modułu wyliczającego i odpowiednie ciągi

Nazwa zasobu Wartość zasobu
Color_Red My String Color in Red
Color_Blue Blueeey
Color_Green Hulk Color

To, co jest naprawdę bardzo miłe w tym, to to, że będzie bardzo pomocne, jeśli potrzebujesz zlokalizować aplikację, ponieważ wszystko, co musisz zrobić, to po prostu utworzyć inny plik zasobów w nowym języku! i Voe-la!


1

Kiedy jestem w takiej sytuacji, proponuję poniższe rozwiązanie.

I jako klasa konsumująca, którą możesz mieć

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

    }
}

I przy użyciu słownika dwukierunkowego: Na podstawie tego ( https://stackoverflow.com/a/255638/986160 ) przy założeniu, że klucze zostaną powiązane z pojedynczymi wartościami w słowniku i podobnym do ( https://stackoverflow.com/a / 255630/986160 ), ale nieco bardziej elegancki. Słownik ten jest również wymienny i możesz przechodzić od ints do string. Poza tym nie musisz mieć żadnych ciągów znaków w bazie kodu, z wyjątkiem tej klasy.

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

1

W przypadku większych zestawów enum struny wymienione przykłady mogą stać się męczące. Jeśli potrzebujesz listy kodów statusu lub listy innych wyliczeń opartych na ciągach znaków, irytujący jest system atrybutów, a konfiguracja ma postać statyczną z instancjami samego siebie. W moim własnym rozwiązaniu korzystam z szablonów T4, aby łatwiej było uzyskać wyliczenia oparte na łańcuchach. Wynik jest podobny do działania klasy HttpMethod.

Możesz użyć tego w następujący sposób:

    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; 

Zaczynasz od pliku Enum.tt.

<#@ 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."
    };
}
#>

Następnie dodajesz plik StringEnum.ttinclude.

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

Na koniec rekompilujesz plik Enum.tt, a dane wyjściowe wyglądają następująco:

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