Enum ToString z ciągami przyjaznymi dla użytkownika

282

Moje wyliczenie składa się z następujących wartości:

private enum PublishStatusses{
    NotCompleted,
    Completed,
    Error
};

Chcę jednak móc wyświetlać te wartości w sposób przyjazny dla użytkownika.
Nie muszę być w stanie ponownie przechodzić od łańcucha do wartości.

Boris Callens
źródło
możliwy duplikat ciągów
znaków
Możliwy duplikat reprezentacji ciągu enum
Liam

Odpowiedzi:

350

Korzystam z Descriptionatrybutu z przestrzeni nazw System.ComponentModel. Po prostu udekoruj enum:

private enum PublishStatusValue
{
    [Description("Not Completed")]
    NotCompleted,
    Completed,
    Error
};

Następnie użyj tego kodu, aby go odzyskać:

public static string GetDescription<T>(this T 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();
}
Ray Booysen
źródło
12
Ten przykład jest łatwiejszy do odczytania. stackoverflow.com/questions/1415140/…
RayLoveless
31
Podejrzewam, że istnieje znaczący wpływ na wydajność w odbiciu opisanym w tym rozwiązaniu. Kod metody Will'a wykorzystującej metodę rozszerzenia ToFriendlyString jest znacznie łatwiejszy do zrozumienia, a jego wydajność również powinna być wyjątkowo szybka.
humbads
1
Podoba mi się wersja, którą łączy @RayL, ponieważ doda tylko metodę rozszerzenia do Enums. Jeśli to wszystko, czego chcesz tego użyć (jak wskazano za pomocą ArgumentException, to nie ma powodu, aby metoda była całkowicie ogólna.
krillgar
4
Oznacza to, że każde wyliczenie potrzebuje własnej metody rozszerzenia. Jest to bardziej ogólne zastosowanie i wymaga więcej pracy, ale prawdopodobnie zdecydujesz się określić, co oznacza „szybki”, zanim zdecydujemy o wydajności.
Ray Booysen
2
@petar, który działa, ale nie, jeśli chcesz, aby przyjazne ciągi były wyświetlane użytkownikom. MY_TYPE będzie miał podkreślenie i nie można go dostosowywać.
Ray Booysen
354

Robię to za pomocą metod rozszerzenia:

public enum ErrorLevel
{
  None,
  Low,
  High,
  SoylentGreen
}

public static class ErrorLevelExtensions
{
  public static string ToFriendlyString(this ErrorLevel me)
  {
    switch(me)
    {
      case ErrorLevel.None:
        return "Everything is OK";
      case ErrorLevel.Low:
        return "SNAFU, if you know what I mean.";
      case ErrorLevel.High:
        return "Reaching TARFU levels";
      case ErrorLevel.SoylentGreen:
        return "ITS PEOPLE!!!!";
      default:
        return "Get your damn dirty hands off me you FILTHY APE!";
    }
  }
}

źródło
6
Jest to o wiele czystsze niż odpowiedź atrybutu. Miły!
pennyrave,
3
@pennyrave: Eh. Wiele składników interfejsu użytkownika oczekuje znalezienia i używania DisplayNameAttribute i DescriptionAttribute. W rzeczywistości teraz używam ich i metody rozszerzenia, aby łatwo uzyskać te wartości.
60
Problem z tym polega na tym, że ciągle piszesz te metody rozszerzenia. Dzięki mechanizmowi atrybutów jest to prosty sposób na dekorowanie go i wywoływanie tylko jednej metody.
Ray Booysen
5
Nie wiem co masz na myśli?
Ray Booysen
9
Moim zdaniem lepiej jest pozwolić, aby defaultimplementacja me.ToString()instrukcji zwróciła i podawała tylko instrukcje case switch dla wartości wyliczenia, które chcesz zastąpić. W twoim przykładzie rozumiem, że wszystkie są różne, ale w rzeczywistych przypadkach użycia, podejrzewam, że większość wartości pojedynczego słowa wyliczeniowego będzie wystarczająca, a ty będziesz nadpisywać tylko wartości wielowymiarowe wyliczeniowe.
Scott
78

Może czegoś mi brakuje, ale co jest nie tak z Enum.GetName?

public string GetName(PublishStatusses value)
{
    return Enum.GetName(typeof(PublishStatusses), value)
}

edycja: dla ciągów przyjaznych dla użytkownika, musisz przejść przez .resource, aby zrobić internacjonalizację / lokalizację, i prawdopodobnie lepiej byłoby użyć stałego klucza opartego na kluczu wyliczającym niż atrybutu dekoratora na tym samym.

annakata
źródło
12
Zwracam dosłowną wartość wyliczenia, nie taką przyjazną dla użytkownika.
Boris Callens
2
oic - cóż, jest całkiem spora sprawa, że ​​musisz wtedy przejrzeć bibliotekę zasobów ciągów opartych na tej wartości, ponieważ alternatywa (atrybuty dekoratora) nie obsługuje I18N
annakata
1
W przypadku I18N sprawiłbym, że metoda GetDescription () szukała w bibliotece zasobów przetłumaczonego ciągu znaków i wróciła do opisu, a następnie do literału.
Boris Callens
3
+1 dla MyEnum.ToString () jako klucz zasobu do lokalizacji. robię to od lat
jackvsworld
1
@annakata w rzeczywistości rozszerzyliśmy mechanizm atrybutów o obsługę l18N, w rzeczywistości jest to prosta zmiana.
Ray Booysen
23

Utworzyłem metodę rozszerzenia wstecznego, aby przekonwertować opis z powrotem na wartość wyliczoną:

public static T ToEnumValue<T>(this string enumerationDescription) where T : struct
{
    var type = typeof(T);

    if (!type.IsEnum)
        throw new ArgumentException("ToEnumValue<T>(): Must be of enum type", "T");

    foreach (object val in System.Enum.GetValues(type))
        if (val.GetDescription<T>() == enumerationDescription)
            return (T)val;

    throw new ArgumentException("ToEnumValue<T>(): Invalid description for enum " + type.Name, "enumerationDescription");
}
Brian Richardson
źródło
15
Przepraszam, ale dziękuję za próbę bycia pomocnym! Ponieważ jest to strona z pytaniami i odpowiedziami, odpowiedzi powinny być próbą bezpośredniej odpowiedzi na pytanie. A pytanie w szczególności stwierdza: „ Nie muszę być w stanie ponownie przechodzić od łańcucha do wartości. Jeszcze raz dziękuję!
Jesse
8
Dzięki za pozytywną krytykę. Zawsze trudno jest być nowym w witrynie i poznać jej kulturę i niuanse. Cieszę się, że są tacy ludzie jak ty, którzy wyprostowali nowych facetów. Jeszcze raz dziękuję, że nie rzuciłeś nowego faceta.
Brian Richardson
6
@Jesse I 4 lata później ktoś chętnie znajduje tutaj kod bjrichardson! SO może być stroną z pytaniami i odpowiedziami, ale nie oznacza to, że pytania są martwe po udzieleniu odpowiedzi.
Jan
18

Najłatwiejszym rozwiązaniem jest użycie niestandardowej metody rozszerzenia (przynajmniej w .NET 3.5 - możesz po prostu przekonwertować ją na statyczną metodę pomocniczą dla wcześniejszych wersji frameworka).

public static string ToCustomString(this PublishStatusses value)
{
    switch(value)
    {
        // Return string depending on value.
    }
    return null;
}

Zakładam tutaj, że chcesz zwrócić coś innego niż faktyczna nazwa wartości wyliczeniowej (którą możesz uzyskać po prostu wywołując ToString).

Noldorin
źródło
Chociaż jest ważny, bardziej podoba mi się ten atrybut. W ten sposób mogę umieścić moją metodę toSTring w osobnej bibliotece, jednocześnie umieszczając niestandardową reprezentację ciągu za pomocą samego wyliczenia
Boris Callens
1
Słusznie. Przypuszczam, że jedną z zalet tej metody jest to, że możesz dołączyć argument do metody określającej pewną zmienną stanu, a następnie zmienić zależną od tego reprezentację ciągu znaków.
Noldorin
1
Tak, wszystko zależy od zakresu metody. Chociaż sposób atrybutu jest bardziej ogólny, twoje rozwiązanie jest bardziej zlokalizowane. Ostatecznie chodzi o potrzeby.
Boris Callens
1
Możesz umieścić metody rozszerzenia w dowolnym miejscu. Musisz tylko odwołać się tam, gdzie chcesz ich użyć.
Tak, ale oznaczałoby to, że ta jedna metoda rozszerzenia powinna być przepisywana za każdym razem, gdy wprowadzasz nowe wyliczenie, dla którego chcesz mieć przyjazną nazwę. Oznaczałoby to również, że WSZYSTKIE aplikacje będą nosić przyjazne nazwy dla WSZYSTKICH innych aplikacji ...
Boris Callens
13

Ten drugi post to Java. Nie można umieszczać metod w Enums w C #.

po prostu zrób coś takiego:

PublishStatusses status = ...
String s = status.ToString();

Jeśli chcesz użyć różnych wartości wyświetlanych dla swoich wartości wyliczeniowych, możesz użyć atrybutów i refleksji.

Lemmy
źródło
3
toString nie jest bezpieczny we wszystkich przypadkach - wyliczenie z wieloma wpisami o tej samej wartości (powiedzmy dla wyliczeń całkowitych) zwróci klucz pierwszej pasującej wartości, a nie klucz testowanego elementu, dlatego Enum.GetName jest preferowany
annakata
4
Cóż, to było najłatwiejsze rozwiązanie dla jego specyficznego wyliczenia
Lemmy
9

Najprostszym sposobem jest włączenie tej klasy rozszerzenia do projektu, będzie ona działać z dowolnym wyliczeniem w projekcie:

public static class EnumExtensions
{
    public static string ToFriendlyString(this Enum code)
    {
        return Enum.GetName(code.GetType(), code);
    }
}

Stosowanie:

enum ExampleEnum
{
    Demo = 0,
    Test = 1, 
    Live = 2
}

...

ExampleEnum ee = ExampleEnum.Live;
Console.WriteLine(ee.ToFriendlyString());
Milan Švec
źródło
2
Tajemnicą jest to, dlaczego ten komentarz nie jest zaakceptowany lub najbardziej uprzywilejowany - bez refleksji, bez zbędnych atrybutów, idealny do prostych sytuacji, w których wyliczenie jest już ładnie nazwane. Możesz pójść o krok dalej i pozwolić na dodanie spacji między dużymi literami przed powrotem „My Enum”.
Vix
12
Jeśli wyliczenie jest już ładnie nazwane, nie ma potrzeby żadnej metody rozszerzenia. Wystarczy użyć istniejącej metody ToString (). string result = "Result: " + ee;
Jan
To powinna być najlepsza odpowiedź. Działa dla każdego wyliczenia. Możesz nawet zaimplementować go za pomocą konkretnego Enum, po prostu zmieniając typ Enum parametru na rzeczywiste Enum, na którym chcesz go użyć.
Juanu Haedo
6
Ta odpowiedź i wszystkie komentarze ignorują pierwotne żądanie rozszerzonego opisu. Całkowicie przegapiliście ćwiczenie polegające na zwróceniu czegoś innego niż domyślna wartość ToString. Nie będę tutaj głosować za wszystkimi notatkami dotyczącymi tej odpowiedzi, ale na pewno chcę.
TonyG
8

Niektóre inne bardziej prymitywne opcje, które unikają klas / typów referencji:

  • Metoda tablicowa
  • Zagnieżdżona metoda struct

Metoda tablicowa

private struct PublishStatusses
{
    public static string[] Desc = {
        "Not Completed",
        "Completed",
        "Error"
    };

    public enum Id
    {
        NotCompleted = 0,
        Completed,
        Error
    };
}

Stosowanie

string desc = PublishStatusses.Desc[(int)PublishStatusses.Id.Completed];

Zagnieżdżona metoda struct

private struct PublishStatusses
{
    public struct NotCompleted
    {
        public const int Id = 0;
        public const string Desc = "Not Completed";
    }

    public struct Completed
    {
        public const int Id = 1;
        public const string Desc = "Completed";
    }

    public struct Error
    {
        public const int Id = 2;
        public const string Desc = "Error";
    }            
}

Stosowanie

int id = PublishStatusses.NotCompleted.Id;
string desc = PublishStatusses.NotCompleted.Desc;

Aktualizacja (03/09/2018)

Hybrydowa metoda przedłużania i pierwsza technika powyżej.

Wolę, aby wyliczenia były definiowane tam, gdzie „należą” (najbliżej źródła pochodzenia, a nie w jakiejś wspólnej, globalnej przestrzeni nazw).

namespace ViewModels
{
    public class RecordVM
    {
        //public enum Enum { Minutes, Hours }
        public struct Enum
        {
            public enum Id { Minutes, Hours }
            public static string[] Name = { "Minute(s)", "Hour(s)" };
        }
    }
}

Wydaje się, że metoda rozszerzenia pasuje do wspólnego obszaru, a „zlokalizowana” definicja wyliczenia sprawia, że ​​metoda rozszerzenia jest bardziej szczegółowa.

namespace Common
{
    public static class EnumExtensions
    {
        public static string Name(this RecordVM.Enum.Id id)
        {
            return RecordVM.Enum.Name[(int)id];
        }
    }   
}

Przykład użycia wyliczenia i jego metody rozszerzenia.

namespace Views
{
    public class RecordView 
    {
        private RecordDataFieldList<string, string> _fieldUnit;

        public RecordView()
        {
            _fieldUnit.List = new IdValueList<string, string>
            {            
                new ListItem<string>((int)RecordVM.Enum.Id.Minutes, RecordVM.Enum.Id.Minutes.Name()),
                new ListItem<string>((int)RecordVM.Enum.Id.Hours, RecordVM.Enum.Id.Hours.Name())
            };
        }

        private void Update()
        {    
            RecordVM.Enum.Id eId = DetermineUnit();

            _fieldUnit.Input.Text = _fieldUnit.List.SetSelected((int)eId).Value;
        }
    }
}

Uwaga: Właściwie postanowiłem wyeliminować Enumopakowanie (i Nametablicę), ponieważ najlepiej jest, aby ciągi nazw pochodziły z zasobu (tj. Pliku konfiguracyjnego lub DB), a nie były na stałe zakodowane, a ponieważ w końcu umieściłem metodę rozszerzenia w ViewModelsprzestrzeń nazw (tylko w innym pliku „CommonVM.cs”). Ponadto cała .Idsprawa staje się rozpraszająca i nieporęczna.

namespace ViewModels
{
    public class RecordVM
    {
        public enum Enum { Minutes, Hours }
        //public struct Enum
        //{
        //    public enum Id { Minutes, Hours }
        //    public static string[] Name = { "Minute(s)", "Hour(s)" };
        //}
    }
}

CommonVM.cs

//namespace Common
namespace ViewModels
{
    public static class EnumExtensions
    {
        public static string Name(this RecordVM.Enum id)
        {
            //return RecordVM.Enum.Name[(int)id];
            switch (id)
            {
                case RecordVM.Enum.Minutes: return "Minute(s)";                    
                case RecordVM.Enum.Hours: return "Hour(s)";
                default: return null;
            }
        }
    }   
}

Przykład użycia wyliczenia i jego metody rozszerzenia.

namespace Views
{
    public class RecordView 
    {
        private RecordDataFieldList<string, string> _fieldUnit

        public RecordView()
        {
            _fieldUnit.List = new IdValueList<string, string>
            {            
                new ListItem<string>((int)RecordVM.Enum.Id.Minutes, RecordVM.Enum.Id.Minutes.Name()),
                new ListItem<string>((int)RecordVM.Enum.Id.Hours, RecordVM.Enum.Id.Hours.Name())
            };
        }

        private void Update()
        {    
            RecordVM.Enum eId = DetermineUnit();

            _fieldUnit.Input.Text = _fieldUnit.List.SetSelected((int)eId).Value;
        }
    }
}
Sam jest
źródło
+ 1-1 = 0 głosów: To rozwiązanie zachowuje składnię Enum i elegancko rozwiązuje problem bez refleksji lub złożonego kodu, więc daje +1. Ale traci cechy samych Enums. Więc chociaż IMO to dobra opcja, nie odpowiada na rzeczywiste pytanie i dostaje -1. Net 0. Przykro nam, nie mamy sposobu, aby nagrać to lepiej w SO.
TonyG
@TonyG Wystarczająco uczciwy. Po pominięciu kilku pytań na temat oceny umiejętności .u pluarlsight.com, zacząłem zdawać sobie sprawę, jak dogłębne są enum C #, więc prawdopodobnie dobrym pomysłem jest przynajmniej wiedzieć o ich możliwościach przed podjęciem decyzji, którą metodologię zastosować (szczególnie w przypadku wszechobecnego użycia, refaktoryzacji może być trochę czasu;).
samis
7

Można użyć Humanizer pakiet z uczłowieczyć wyliczenia zabudowy mogły. Przykład:

enum PublishStatusses
{
    [Description("Custom description")]
    NotCompleted,
    AlmostCompleted,
    Error
};

możesz użyć Humanizemetody rozszerzenia bezpośrednio na enum:

var st1 = PublishStatusses.NotCompleted;
var str1 = st1.Humanize(); // will result in Custom description

var st2 = PublishStatusses.AlmostCompleted;
var str2 = st2.Humanize(); // will result in Almost completed (calculated automaticaly)
Konrad Kokosa
źródło
Wykorzystuje również odbicie i nie jest buforowany. github.com/Humanizr/Humanizer/blob/…
Konrad
Będzie tak powolny, jak rozwiązanie w pierwszej odpowiedzi Raya
Konrada
5

W przypadku Raya Booysena w kodzie jest błąd: Enum ToString z przyjaznymi dla użytkownika ciągami znaków

Musisz uwzględnić wiele atrybutów wartości wyliczeniowych.

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 && attrs.Where(t => t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault() != null)
            {
                //Pull out the description value
                return ((DescriptionAttribute)attrs.Where(t=>t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault()).Description;
            }
        }
        //If we have no description attribute, just return the ToString of the enum
        return enumerationValue.ToString();
Joel MC
źródło
4
Pominięcie sprawdzenia wielu atrybutów opisu jest celowe. Jeśli wyliczenie ma dwa, a używasz do wygenerowania opisu, chciałbym pomyśleć, że jest to wyjątkowa okoliczność. Myślę, że faktycznym błędem jest to, że nie robię Single (), aby zgłosić wyjątek. W przeciwnym razie cała sygnatura metody nie ma sensu. GetDescription ()? Który opis? Agregat?
Ray Booysen
4
public enum MyEnum
{
    [Description("Option One")]
    Option_One
}

public static string ToDescriptionString(this Enum This)
{
    Type type = This.GetType();

    string name = Enum.GetName(type, This);

    MemberInfo member = type.GetMembers()
        .Where(w => w.Name == name)
        .FirstOrDefault();

    DescriptionAttribute attribute = member != null
        ? member.GetCustomAttributes(true)
            .Where(w => w.GetType() == typeof(DescriptionAttribute))
            .FirstOrDefault() as DescriptionAttribute
        : null;

    return attribute != null ? attribute.Description : name;
}
Diogo Freitas
źródło
3
Zawsze miło jest napisać tekst wyjaśniający, dlaczego to powinno działać i dlaczego OP nie.
phaberest
Tylko konwencje FYI, C # wymagają zmiennych lokalnych i parametrów metody z małą literą początkową. Jednym wyjątkiem jest thisparametr metod rozszerzeń, który można zobaczyć Thisw wielu przykładach w Internecie. Wywołanie go w taki sam sposób, jak zrobiłeś ( Enum Enum), czyni kod mniej czytelnym.
Massimiliano Kraus
4

Zamiast używać wyliczenia użyj klasy statycznej.

zastąpić

private enum PublishStatuses{
    NotCompleted,
    Completed,
    Error
};

z

private static class PublishStatuses{
    public static readonly string NotCompleted = "Not Completed";
    public static readonly string Completed = "Completed";
    public static readonly string Error = "Error";
};

będzie używany w ten sposób

PublishStatuses.NotCompleted; // "Not Completed"

Problem z wykorzystaniem najlepszych rozwiązań „metody rozszerzenia”:

Prywatne wyliczenie jest często używane w innej klasie. Rozwiązanie metody rozszerzenia nie jest tam poprawne, ponieważ musi należeć do własnej klasy. To rozwiązanie może być prywatne i osadzone w innej klasie.

Asher Girlanda
źródło
2

Zdarza się, że jestem fanem VB.NET, więc oto moja wersja, łącząc metodę DescriptionAttribute z metodą rozszerzenia. Po pierwsze, wyniki:

Imports System.ComponentModel ' For <Description>

Module Module1
  ''' <summary>
  ''' An Enum type with three values and descriptions
  ''' </summary>
  Public Enum EnumType
    <Description("One")>
    V1 = 1

    ' This one has no description
    V2 = 2

    <Description("Three")>
    V3 = 3
  End Enum

  Sub Main()
    ' Description method is an extension in EnumExtensions
    For Each v As EnumType In [Enum].GetValues(GetType(EnumType))
      Console.WriteLine("Enum {0} has value {1} and description {2}",
        v,
        CInt(v),
        v.Description
      )
    Next
    ' Output:
    ' Enum V1 has value 1 and description One
    ' Enum V2 has value 2 and description V2
    ' Enum V3 has value 3 and description Three
  End Sub
End Module

Podstawowe rzeczy: wyliczenie o nazwie EnumType z trzema wartościami V1, V2 i V3. „Magia” występuje w wywołaniu Console.WriteLine w Sub Main (), gdzie ostatni argument jest po prostu v.Description. Zwraca „One” dla V1, „V2” dla V2 i „Three” dla V3. Ta metoda opisu jest w rzeczywistości metodą rozszerzenia, zdefiniowaną w innym module o nazwie EnumExtensions:

Option Strict On
Option Explicit On
Option Infer Off

Imports System.Runtime.CompilerServices
Imports System.Reflection
Imports System.ComponentModel

Module EnumExtensions
  Private _Descriptions As New Dictionary(Of String, String)

  ''' <summary>
  ''' This extension method adds a Description method
  ''' to all enum members. The result of the method is the
  ''' value of the Description attribute if present, else
  ''' the normal ToString() representation of the enum value.
  ''' </summary>
  <Extension>
  Public Function Description(e As [Enum]) As String
    ' Get the type of the enum
    Dim enumType As Type = e.GetType()
    ' Get the name of the enum value
    Dim name As String = e.ToString()

    ' Construct a full name for this enum value
    Dim fullName As String = enumType.FullName + "." + name

    ' See if we have looked it up earlier
    Dim enumDescription As String = Nothing
    If _Descriptions.TryGetValue(fullName, enumDescription) Then
      ' Yes we have - return previous value
      Return enumDescription
    End If

    ' Find the value of the Description attribute on this enum value
    Dim members As MemberInfo() = enumType.GetMember(name)
    If members IsNot Nothing AndAlso members.Length > 0 Then
      Dim descriptions() As Object = members(0).GetCustomAttributes(GetType(DescriptionAttribute), False)
      If descriptions IsNot Nothing AndAlso descriptions.Length > 0 Then
        ' Set name to description found
        name = DirectCast(descriptions(0), DescriptionAttribute).Description
      End If
    End If

    ' Save the name in the dictionary:
    _Descriptions.Add(fullName, name)

    ' Return the name
    Return name
  End Function
End Module

Ponieważ wyszukiwanie atrybutów opisu przy użyciu Reflectionjest powolne, wyszukiwania są również buforowane w pamięci prywatnej Dictionary, która jest wypełniana na żądanie.

(Przepraszamy za rozwiązanie VB.NET - przetłumaczenie go na język C # powinno być względnie łatwe, a mój język C # zardzewiałe na nowe tematy, takie jak rozszerzenia)

MarkusT
źródło
2

Czyste podsumowanie powyższych sugestii z próbką:

namespace EnumExtensions {

using System;
using System.Reflection;

public class TextAttribute : Attribute {
   public string Text;
   public TextAttribute( string text ) {
      Text = text;
   }//ctor
}// class TextAttribute

public static class EnumExtender {

public static string ToText( this Enum enumeration ) {

   MemberInfo[] memberInfo = enumeration.GetType().GetMember( enumeration.ToString() );

   if ( memberInfo != null && memberInfo.Length > 0 ) {

      object[] attributes = memberInfo[ 0 ].GetCustomAttributes( typeof(TextAttribute),  false );

      if ( attributes != null && attributes.Length > 0 ) {
         return ( (TextAttribute)attributes[ 0 ] ).Text;
      }

   }//if

   return enumeration.ToString();

}//ToText

}//class EnumExtender

}//namespace

STOSOWANIE:

using System;
using EnumExtensions;

class Program {

public enum Appearance {

  [Text( "left-handed" ) ]
  Left,

  [Text( "right-handed" ) ]
  Right,

}//enum

static void Main( string[] args ) {

   var appearance = Appearance.Left;
   Console.WriteLine( appearance.ToText() );

}//Main

}//class
podkreślać
źródło
1
W C # istnieje atrybut [Opis („”)], dlaczego nie użyć tego?
Stefan Koenen,
Oczywiście użycie [Opis („”)] jest dobrym rozwiązaniem. Ale chciałem, żeby próbka była kompletna.
podkreślono
2

Użyj Enum.GetName

Z powyższego linku ...

using System;

public class GetNameTest {
    enum Colors { Red, Green, Blue, Yellow };
    enum Styles { Plaid, Striped, Tartan, Corduroy };

    public static void Main() {

        Console.WriteLine("The 4th value of the Colors Enum is {0}", Enum.GetName(typeof(Colors), 3));
        Console.WriteLine("The 4th value of the Styles Enum is {0}", Enum.GetName(typeof(Styles), 3));
    }
}
// The example displays the following output:
//       The 4th value of the Colors Enum is Yellow
//       The 4th value of the Styles Enum is Corduroy
JMH
źródło
2

Zgodnie z tą dokumentacją: https://docs.microsoft.com/pt-br/dotnet/api/system.enum.tostring?view=netframework-4.8

Można po prostu przekonwertować moduł wyliczający na ciąg znaków, używając formatu takiego jak ten:

public enum Example
{
    Example1,
    Example2
}

Console.WriteLine(Example.Example1.ToString("g"));

//Outputs: "Example1"

Wszystkie możliwe formaty można zobaczyć w tym linku: https://docs.microsoft.com/pt-br/dotnet/api/system.string?view=netframework-4.8

Emanuel Hiroshi
źródło
1

Jest to aktualizacja kodu Raya Booysena, który używa ogólnej metody GetCustomAttributes i LINQ, aby uczynić wszystko nieco bardziej uporządkowanym.

    /// <summary>
    /// Gets the value of the <see cref="T:System.ComponentModel.DescriptionAttribute"/> on an struct, including enums.  
    /// </summary>
    /// <typeparam name="T">The type of the struct.</typeparam>
    /// <param name="enumerationValue">A value of type <see cref="T:System.Enum"/></param>
    /// <returns>If the struct has a Description attribute, this method returns the description.  Otherwise it just calls ToString() on the struct.</returns>
    /// <remarks>Based on http://stackoverflow.com/questions/479410/enum-tostring/479417#479417, but useful for any struct.</remarks>
    public static string GetDescription<T>(this T enumerationValue) where T : struct
    {
        return enumerationValue.GetType().GetMember(enumerationValue.ToString())
                .SelectMany(mi => mi.GetCustomAttributes<DescriptionAttribute>(false),
                    (mi, ca) => ca.Description)
                .FirstOrDefault() ?? enumerationValue.ToString();
    }   
Richard Anthony Hein
źródło
Nie rozumiesz, dlaczego potrzebujesz, aby był ogólny? Jeśli zamierzasz użyć odbicia?
Lee Louviere,
@LeeLouviere Głównie, aby uniknąć boksowania, gdy struct (typ wartości) jest przekazywany jako parametr.
Richard Anthony Hein
1
zamiast tego numerationValue.GetType () użyj: typeof (T).
Slava
1
Ogromna poprawka o jedną linię w stosunku do zaakceptowanej odpowiedzi bez utraty czytelności (YMMV). Tak, z typeof (T).
TonyG
1

Jeszcze czystsze podsumowanie:

using System;
using System.Reflection;

public class TextAttribute : Attribute
{
    public string Text;
    public TextAttribute(string text)
    {
        Text = text;
    }
}  

public static class EnumExtender
{
    public static string ToText(this Enum enumeration)
    {
        var memberInfo = enumeration.GetType().GetMember(enumeration.ToString());
        if (memberInfo.Length <= 0) return enumeration.ToString();

        var attributes = memberInfo[0].GetCustomAttributes(typeof(TextAttribute), false);
        return attributes.Length > 0 ? ((TextAttribute)attributes[0]).Text : enumeration.ToString();
    }
}

Takie samo użycie jak w przypadku podkreślenia.

StefanDK
źródło
0

Do wyliczania flag, w tym.

    public static string Description(this Enum value)
    {
        Type type = value.GetType();

        List<string> res = new List<string>();
        var arrValue = value.ToString().Split(',').Select(v=>v.Trim());
        foreach (string strValue in arrValue)
        {
            MemberInfo[] memberInfo = type.GetMember(strValue);
            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0 && attrs.Where(t => t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault() != null)
                {
                    res.Add(((DescriptionAttribute)attrs.Where(t => t.GetType() == typeof(DescriptionAttribute)).FirstOrDefault()).Description);
                }
                else
                    res.Add(strValue);
            }
            else
                res.Add(strValue);
        }

        return res.Aggregate((s,v)=>s+", "+v);
    }
Lukich
źródło
0

Jeśli chcesz dodać spację między słowami, jest to tak proste, jak

string res = Regex.Replace(PublishStatusses.NotCompleted, "[A-Z]", " $0").Trim();
VladL
źródło
0

Używam klasy ogólnej do przechowywania par wyliczenia / opisu i zagnieżdżonej klasy pomocnika, aby uzyskać opis.

Enum :

enum Status { Success, Fail, Pending }

Klasa ogólna:

Uwaga: Ponieważ klasa ogólna nie może być ograniczona przez wyliczanie , ograniczam je przez struct i sprawdzam, czy w konstruktorze występuje wyliczanie .

public class EnumX<T> where T : struct
{
    public T Code { get; set; }
    public string Description { get; set; }

    public EnumX(T code, string desc)
    {
        if (!typeof(T).IsEnum) throw new NotImplementedException();

        Code = code;
        Description = desc;
    }

    public class Helper
    {
        private List<EnumX<T>> codes;

        public Helper(List<EnumX<T>> codes)
        {
            this.codes = codes;
        }

        public string GetDescription(T code)
        {
            EnumX<T> e = codes.Where(c => c.Code.Equals(code)).FirstOrDefault();
            return e is null ? "Undefined" : e.Description;
        }
    }
}

Stosowanie:

EnumX<Status>.Helper StatusCodes = new EnumX<Status>.Helper(new List<EnumX<Status>>()
        {
            new EnumX<Status>(Status.Success,"Operation was successful"),
            new EnumX<Status>(Status.Fail,"Operation failed"),
            new EnumX<Status>(Status.Pending,"Operation not complete. Please wait...")
        });

        Console.WriteLine(StatusCodes.GetDescription(Status.Pending));
zisha
źródło
-2

Myślę, że najlepszym (i najłatwiejszym) sposobem rozwiązania problemu jest napisanie metody rozszerzenia dla Twojego wyliczenia:

public static string GetUserFriendlyString(this PublishStatusses status)
    {

    }
użytkownik7096584
źródło
1
Ktoś był 7 lat wcześniej i stwierdził, że
Steven