Representasi string dari Enum

912

Saya memiliki enumerasi berikut:

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

Namun masalahnya adalah bahwa saya memerlukan kata "BENTUK" ketika saya meminta AuthenticationMethod.FORMS dan bukan id 1.

Saya telah menemukan solusi berikut untuk masalah ini ( tautan ):

Pertama, saya perlu membuat atribut khusus yang disebut "StringValue":

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

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

    public string Value
    {
        get { return _value; }
    }

}

Kemudian saya dapat menambahkan atribut ini ke enumerator saya:

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

Dan tentu saja saya perlu sesuatu untuk mengambil StringValue itu:

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

Bagus sekarang saya punya alat untuk mendapatkan nilai string untuk enumerator. Saya kemudian dapat menggunakannya seperti ini:

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

Oke sekarang semua ini bekerja seperti pesona tetapi saya merasa banyak pekerjaan. Saya bertanya-tanya apakah ada solusi yang lebih baik untuk ini.

Saya juga mencoba sesuatu dengan kamus dan properti statis tetapi itu juga tidak lebih baik.

pengguna29964
sumber
8
Bagus! Saya bisa menggunakan ini untuk menerjemahkan nilai enum ke string terlokalisasi.
Øyvind Skaar
5
Meskipun Anda mungkin menemukan ini panjang lebar, sebenarnya cara yang cukup fleksibel untuk hal-hal lain. Seperti yang ditunjukkan oleh salah satu rekan saya, ini dapat digunakan dalam banyak kasus untuk menggantikan Enum Helpers yang memetakan kode basis data ke nilai enum, dll ... ...
BenAlabaster
27
MSDN merekomendasikan kelas atribut sufiks dengan sufiks "Atribut". Jadi "class StringValueAttribute";)
serhio
14
Saya setuju dengan @BenAlabaster ini sebenarnya cukup fleksibel. Selain itu, Anda dapat menjadikan ini metode ekstensi hanya dengan menambahkan thisdi depan Enumdalam metode statis Anda. Maka Anda bisa melakukannya AuthenticationMethod.Forms.GetStringValue();
Justin Pihony
5
Pendekatan ini menggunakan refleksi untuk membaca nilai atribut dan sangat lambat jika Anda harus memanggil GetStringValue () berkali-kali dalam pengalaman saya. Pola type-safe-enum lebih cepat.
Rn222

Jawaban:

868

Cobalah tipe-safe-enum pattern.

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

}

Pembaruan Konversi tipe eksplisit (atau implisit) dapat dilakukan oleh

  • menambahkan bidang statis dengan pemetaan

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • nb Agar inisialisasi bidang "anggota enum" tidak melempar NullReferenceException saat memanggil konstruktor instance, pastikan untuk meletakkan bidang Kamus sebelum bidang "anggota enum" di kelas Anda. Ini karena inisialisasi bidang statis dipanggil dalam urutan deklarasi, dan sebelum konstruktor statis, menciptakan situasi yang aneh dan perlu tetapi membingungkan bahwa konstruktor instance dapat dipanggil sebelum semua bidang statis telah diinisialisasi, dan sebelum konstruktor statis dipanggil.
  • mengisi pemetaan ini di konstruktor contoh

    instance[name] = this;
  • dan menambahkan operator konversi tipe yang ditentukan pengguna

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }
    
Jakub Šturc
sumber
17
Itu terlihat seperti enum tetapi bukan enum. Saya bisa membayangkan bahwa menyebabkan beberapa masalah menarik jika orang mulai mencoba membandingkan AuthenticationMethods. Anda mungkin perlu membebani berbagai operator kesetaraan juga.
Semut
36
@ Tidak: Saya tidak harus. Karena kita hanya memiliki satu instance dari setiap AuthenticationMethod, persamaan referensi yang diwarisi dari Object berfungsi dengan baik.
Jakub Šturc
10
@tyriker: Kompiler tidak. Konstruktor bersifat pribadi sehingga Anda tidak dapat membuat instance baru. Anggota statis juga tidak dapat diakses melalui instance.
Jakub Šturc
21
@ Jakub Sangat menarik. Saya harus bermain dengannya untuk mengetahui cara menggunakannya, dan menyadari manfaatnya. Ini kelas publik, non-statis, tetapi tidak dapat dipakai dan Anda hanya dapat mengakses anggota statisnya. Pada dasarnya, itu berperilaku seperti enum. Tetapi yang terbaik ... anggota statis diketik dari kelas dan bukan string atau int generik. Ini ... [tunggu sebentar] ... ketik safe enum! Terima kasih telah membantu saya memahami.
tyriker
6
@kiran Saya telah mengirim versi yang sedikit dimodifikasi dari jawaban Jakub Šturc di bawah ini yang memungkinkannya untuk digunakan dengan pernyataan Switch-Case, jadi sekarang tidak ada kerugian untuk pendekatan ini :)
deadlydog
228

Gunakan metode

Enum.GetName(Type MyEnumType,  object enumvariable)  

seperti dalam (Asumsikan Shipperadalah Enum yang ditentukan)

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

Ada banyak metode statis lainnya di kelas Enum yang layak diselidiki juga ...

Charles Bretana
sumber
5
Persis. Saya memang membuat atribut khusus untuk deskripsi string, tapi itu karena saya ingin versi yang mudah digunakan (dengan spasi dan karakter khusus lainnya) yang dapat dengan mudah terikat ke ComboBox atau semacamnya.
lc.
5
Enum.GetName mencerminkan nama bidang dalam enum - sama dengan .ToString (). Jika kinerja adalah masalah, itu bisa menjadi masalah. Saya tidak akan khawatir tentang hal itu kecuali jika Anda mengkonversi banyak enum.
Keith
8
Pilihan lain untuk dipertimbangkan, jika Anda memerlukan enum dengan fungsi ekstra, adalah "roll sendiri" menggunakan sebuah struct ... Anda menambahkan properti statis yang hanya diberi nama untuk mewakili nilai enum yang diinisialisasi ke konstruktor yang menghasilkan contoh individual dari struct ...
Charles Bretana
1
maka Anda dapat menambahkan anggota struct lain apa pun yang Anda inginkan, untuk mengimplementasikan fungsi apa pun yang Anda inginkan "enum" ini ...
Charles Bretana
2
Masalahnya di sini adalah GetName tidak dapat dilokalisasi. Itu tidak selalu menjadi perhatian, tetapi itu adalah sesuatu yang harus diperhatikan.
Joel Coehoorn
79

Anda dapat merujuk nama daripada nilainya dengan menggunakan ToString ()

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

Dokumentasi di sini:

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

... dan jika Anda memberi nama enum Anda dalam Pascal Case (seperti yang saya lakukan - seperti ThisIsMyEnumValue = 1 dll.) maka Anda dapat menggunakan regex yang sangat sederhana untuk mencetak formulir ramah:

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

yang dapat dengan mudah dipanggil dari sembarang string:

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

Output:

Konversi Kasus Pascal Gila Saya ke Kasus Ramah

Itu menghemat menjalankan semua jalan di sekitar rumah-rumah menciptakan atribut khusus dan melampirkannya ke enum Anda atau menggunakan tabel pencarian untuk mengawinkan nilai enum dengan string yang ramah dan yang terbaik dari semua itu mengelola sendiri dan dapat digunakan pada string Pascal Case apa pun yang tak terbatas lebih dapat digunakan kembali. Tentu saja, itu tidak memungkinkan Anda untuk memiliki nama ramah yang berbeda dari enum yang disediakan oleh solusi Anda.

Saya suka solusi orisinal Anda meskipun untuk skenario yang lebih kompleks. Anda bisa mengambil solusi Anda satu langkah lebih jauh dan menjadikan GetStringValue Anda sebagai metode ekstensi enum Anda dan kemudian Anda tidak perlu referensi seperti StringEnum. DapatkanStringValue ...

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

Anda kemudian dapat mengaksesnya dengan mudah langsung dari instance enum Anda:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
BenAlabaster
sumber
2
Ini tidak membantu jika "nama yang bersahabat" membutuhkan tempat. Seperti "Formulir Otentikasi"
Ray Booysen
4
Jadi pastikan enum diberi nama dengan penutup seperti FormsAuthentication dan masukkan spasi sebelum tutup yang tidak ada di awal. Ini bukan ilmu roket untuk menyisipkan spasi dalam string ...
BenAlabaster
4
Penentuan spasi otomatis dari nama Pascal Case menjadi bermasalah jika mengandung singkatan yang harus ditulis dengan huruf besar, XML atau GPS.
Richard Ev
2
@ RichardEv, tidak ada regex yang sempurna untuk ini, tetapi di sini ada yang seharusnya bekerja sedikit lebih baik dengan singkatan. "(?!^)([^A-Z])([A-Z])", "$1 $2". Jadi HereIsATESTmenjadi Here Is ATEST.
sparebytes
Tidak elegan melakukan "peretasan" kecil ini. Saya mendapatkan apa yang OP katakan dan saya mencoba untuk menemukan solusi yang sama yaitu menggunakan keanggunan Enums tetapi dapat dengan mudah mengakses pesan terkait. Satu-satunya solusi yang dapat saya pikirkan adalah menerapkan semacam pemetaan antara nama enum dan nilai string tetapi itu tidak mengatasi masalah mempertahankan data string (namun membuatnya praktis untuk skenario di mana Anda perlu memiliki multi daerah dll )
Tahir Khalid
72

Sayangnya refleksi untuk mendapatkan atribut pada enum cukup lambat:

Lihat pertanyaan ini: Adakah yang tahu cara cepat untuk mendapatkan atribut khusus pada nilai enum?

Ini .ToString()juga cukup lambat pada enum.

Anda dapat menulis metode ekstensi untuk enum:

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

Ini tidak bagus, tetapi akan cepat dan tidak memerlukan refleksi untuk atribut atau nama bidang.


Pembaruan C # 6

Jika Anda dapat menggunakan C # 6 maka nameofoperator baru berfungsi untuk enum, sehingga nameof(MyEnum.WINDOWSAUTHENTICATION)akan dikonversi menjadi "WINDOWSAUTHENTICATION"pada waktu kompilasi , menjadikannya cara tercepat untuk mendapatkan nama enum.

Perhatikan bahwa ini akan mengonversi enum eksplisit ke konstanta inline, jadi itu tidak berfungsi untuk enum yang Anda miliki dalam variabel. Begitu:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Tapi...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"
Keith
sumber
24
Anda bisa mengambil nilai atribut sekali dan meletakkannya di Kamus <MyEnum, string> untuk menjaga aspek deklaratif.
Jon Skeet
1
Ya itulah yang akhirnya kami lakukan di aplikasi dengan banyak enum ketika kami menemukan bahwa refleksi adalah leher botol.
Keith
Terima kasih Jon dan Keith, saya akhirnya menggunakan saran Kamus Anda. Bekerja dengan baik (dan cepat!).
Helge Klein
@ JonSkeet Saya tahu ini sudah tua. Tetapi bagaimana seseorang mencapai ini?
user919426
2
@ user919426: Mencapai keinginan? Menempatkan mereka di kamus? Cukup buat kamus, idealnya dengan penginisialisasi koleksi ... tidak jelas apa yang Anda minta.
Jon Skeet
59

Saya menggunakan metode ekstensi:

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

Sekarang hiasi enumdengan:

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

Saat Anda menelepon

AuthenticationMethod.FORMS.ToDescription()kamu akan mendapatkan "FORMS".

Mangesh Pimpalkar
sumber
1
Saya harus menambahkan using System.ComponentModel;Juga, metode ini hanya berfungsi jika Anda ingin nilai String sama dengan nama Enum. OP menginginkan nilai yang berbeda.
elcool
2
Bukankah maksud Anda saat menelepon AuthenticationMethod.FORMS.ToDescription()?
nicodemus13
41

Cukup gunakan ToString()metode ini

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

Untuk referensi string Tomato, cukup gunakan

any.Tomato.ToString();
chepe
sumber
Wow. Itu mudah. Saya tahu OP ingin menambahkan deskripsi string khusus, tetapi inilah yang saya butuhkan. Saya seharusnya tahu untuk mencoba ini, dalam retrospeksi, tetapi saya pergi ke rute Enum.
Rafe
7
Mengapa semua orang terlalu rumit?
Brent
18
@Brent Karena paling sering Anda memiliki .ToString()nilai berbeda dari nilai ramah pengguna yang Anda butuhkan.
Novitchi S
2
@ Bos - karena ini berbeda dari pertanyaan yang diajukan. Pertanyaan yang diajukan adalah bagaimana Anda mendapatkan string ini dari variabel yang telah diberi nilai enumerasi. Itu dinamis saat dijalankan. Ini memeriksa definisi jenis dan diatur pada waktu berjalan.
Hogan
1
@Hogan - ToString () bekerja pada variabel juga: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV
29

Solusi yang sangat sederhana untuk ini dengan .Net 4.0 dan di atasnya. Tidak diperlukan kode lain.

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

Untuk mendapatkan string tentang cukup gunakan:

MyStatus.Active.ToString("f");

atau

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

Nilai akan "Aktif" atau "Diarsipkan".

Untuk melihat format string yang berbeda ("f" dari atas) saat memanggil Enum.ToStringlihat halaman Enumeration Format Strings ini

David C
sumber
28

Saya menggunakan atribut Deskripsi dari namespace System.ComponentModel. Cukup hiasi enum dan kemudian gunakan kode ini untuk mengambilnya:

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

        }

Sebagai contoh:

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

Kode ini dapat digunakan untuk enum di mana Anda tidak memerlukan "Nama yang ramah" dan hanya akan mengembalikan .ToString () dari enum.

Ray Booysen
sumber
27

Saya sangat menyukai jawaban Jakub Šturc, tetapi kekurangannya adalah Anda tidak dapat menggunakannya dengan pernyataan sakelar kasus. Inilah versi yang sedikit dimodifikasi dari jawabannya yang dapat digunakan dengan pernyataan pergantian:

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

Jadi Anda mendapatkan semua manfaat dari jawaban Jakub Šturc, ditambah kita dapat menggunakannya dengan pernyataan pergantian seperti:

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;      
}
deadlydog
sumber
Solusi yang lebih pendek adalah dengan menghapus enum {} dan sebagai gantinya tetap menghitung statis berapa banyak Enum yang telah Anda buat. Ini juga memberi manfaat bahwa Anda tidak perlu menambahkan contoh baru yang Anda buat ke daftar enum. misalnya public static int nextAvailable { get; private set; }kemudian di konstruktorthis.Value = nextAvailable++;
kjhf
Ide menarik @kjhf. Kekhawatiran saya adalah bahwa jika seseorang menata ulang kode, maka nilai yang ditetapkan untuk nilai enum mungkin berubah juga. Sebagai contoh, ini dapat mengakibatkan nilai enum yang salah diambil ketika nilai enum disimpan ke file / database, urutan baris "baru AuthenticationMethod (...)" diubah (misalnya satu dihapus), dan kemudian menjalankan aplikasi lagi dan mengambil nilai enum dari file / database; nilai enum mungkin tidak cocok dengan AuthenticationMethod yang awalnya disimpan.
deadlydog
Poin bagus - meskipun saya berharap dalam kasus-kasus khusus ini orang tidak akan bergantung pada nilai integer enum (atau menyusun ulang kode enum.) - dan nilai ini murni digunakan sebagai saklar dan mungkin alternatif dari .Equals () dan. GetHashCode (). Jika khawatir, Anda selalu bisa memberikan komentar besar dengan "JANGAN REORDER": p
kjhf
Tidak bisakah Anda membebani =operator terlalu banyak untuk memungkinkan pergantian bekerja? Saya melakukan ini di VB dan sekarang dapat menggunakannya dalam select casepernyataan.
user1318499
@ user1318499 Tidak, C # memiliki aturan yang lebih ketat di sekitar pernyataan sakelar daripada VB. Anda tidak dapat menggunakan instance kelas untuk pernyataan Kasus; Anda hanya dapat menggunakan primitif konstan.
deadlydog
13

Saya menggunakan kombinasi beberapa saran di atas, dikombinasikan dengan caching. Sekarang, saya mendapat ide dari beberapa kode yang saya temukan di suatu tempat di internet, tetapi saya tidak dapat mengingat di mana saya mendapatkannya atau menemukannya. Jadi, jika ada orang yang menemukan sesuatu yang mirip, silakan berkomentar dengan atribusi.

Lagi pula, penggunaannya melibatkan konverter tipe, jadi jika Anda mengikat ke UI itu 'hanya berfungsi'. Anda dapat memperluas dengan pola Jakub untuk pencarian kode cepat dengan menginisialisasi dari konverter tipe ke metode statis.

Penggunaan basis akan terlihat seperti ini

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

Kode untuk konverter tipe enum kustom berikut:

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

}

Steve Mitcham
sumber
12

Dalam pertanyaan Anda, Anda tidak pernah mengatakan bahwa Anda benar-benar membutuhkan nilai numerik enum di mana saja.

Jika Anda tidak dan hanya perlu enum tipe string (yang bukan tipe integral sehingga tidak bisa menjadi basis enum) di sini adalah caranya:

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

Anda dapat menggunakan sintaks yang sama dengan enum untuk referensi

if (bla == AuthenticationMethod.FORMS)

Ini akan sedikit lebih lambat dibandingkan dengan nilai numerik (membandingkan string bukan angka) tetapi di sisi positifnya ia tidak menggunakan refleksi (lambat) untuk mengakses string.

ILIA BROUDNO
sumber
jika Anda menggunakan "const" bukannya "static readonly" maka Anda dapat menggunakan nilai-nilai sebagai label kasus dalam pernyataan switch.
Ed N.
11

Bagaimana saya memecahkan ini sebagai metode ekstensi:

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
}

Penggunaan (di mana o.OrderType adalah properti dengan nama yang sama dengan enum):

o.OrderType.GetDescription()

Yang memberi saya serangkaian "Kartu Baru" atau "Muat Ulang" alih-alih nilai aktual NewCard dan Isi Ulang.

Detik
sumber
Untuk kelengkapan Anda harus menyertakan salinan kelas DescriptionAttribute Anda.
Bernie White
3
Bernie, KeteranganAttribute ada di System.ComponentModel
agentnega
11

Pembaruan: Mengunjungi halaman ini, 8 tahun kemudian, setelah tidak menyentuh C # untuk waktu yang lama, sepertinya jawaban saya bukan lagi solusi terbaik. Saya sangat suka solusi konverter diikat dengan atribut-fungsi.

Jika Anda membaca ini, pastikan Anda juga memeriksa jawaban lain.
(petunjuk: mereka di atas yang ini)


Seperti kebanyakan dari Anda, saya sangat menyukai jawaban yang dipilih oleh Jakub Šturc , tetapi saya juga sangat benci menyalin kode tempel, dan mencoba melakukannya sesedikit mungkin.

Jadi saya memutuskan saya ingin kelas EnumBase dari mana sebagian besar fungsi diwarisi / bawaan, membuat saya fokus pada konten alih-alih perilaku.

Masalah utama dengan pendekatan ini didasarkan pada fakta bahwa meskipun nilai-nilai Enum adalah instance tipe-aman, interaksinya adalah dengan implementasi Statis dari tipe Kelas Enum. Jadi dengan sedikit bantuan sihir generik, saya pikir saya akhirnya mendapatkan campuran yang benar. Semoga seseorang menemukan ini berguna seperti yang saya lakukan.

Saya akan mulai dengan contoh Jakub, tetapi menggunakan warisan dan obat generik:

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

Dan di sini adalah kelas dasar:

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
}
Tukang Kunci
sumber
Anda mungkin dapat memanggil konstruktor statis yang diturunkan dari konstruktor statis dasar. Saya masih mencari ke dalamnya, tapi sejauh ini saya tidak menemukan masalah dengan itu: stackoverflow.com/questions/55290034/…
Cory-G
10

Saya setuju dengan Keith, tetapi saya tidak dapat memilih (belum).

Saya menggunakan metode statis dan pernyataan swith untuk mengembalikan apa yang saya inginkan. Dalam database saya menyimpan tinyint dan kode saya hanya menggunakan enum yang sebenarnya, jadi string adalah untuk persyaratan UI. Setelah banyak pengujian, ini menghasilkan kinerja terbaik dan sebagian besar kontrol atas output.

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

Namun, oleh beberapa akun, ini mengarah pada kemungkinan mimpi buruk pemeliharaan dan beberapa bau kode. Saya mencoba untuk mengawasi enum yang panjang dan banyak enum, atau yang sering berubah. Kalau tidak, ini merupakan solusi yang bagus untuk saya.

Tony Basallo
sumber
10

Jika Anda datang ke sini mencari untuk menerapkan "Enum" sederhana tetapi yang nilainya adalah string bukan ints, berikut adalah solusi paling sederhana:

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

Penerapan:

var someStringVariable = MetricValueList.Brand;
Grinn
sumber
2
Mungkin lebih baik untuk membuat variabel const daripada menggunakan static readonly.
AndyGeek
1
const tidak baik untuk kelas yang dapat diakses publik, karena mereka dipanggang dalam waktu kompilasi, Anda tidak dapat mengganti DLL pihak ketiga tanpa mengkompilasi ulang seluruh kode Anda dengan const. Kinerja offset dari const vs static readonly dapat diabaikan.
Kristian Williams
7

Ketika saya dihadapkan dengan masalah ini, ada beberapa pertanyaan yang saya coba cari jawabannya terlebih dahulu:

  • Apakah nama nilai enum saya cukup ramah untuk tujuan itu, atau apakah saya perlu memberikan yang lebih ramah?
  • Apakah saya perlu pulang pergi? Yaitu, apakah saya perlu mengambil nilai teks dan menguraikannya menjadi nilai enum?
  • Apakah ini sesuatu yang perlu saya lakukan untuk banyak enum di proyek saya, atau hanya satu?
  • Elemen UI seperti apa yang akan saya sajikan informasi ini - khususnya, apakah saya akan mengikat ke UI, atau menggunakan lembar properti?
  • Apakah ini perlu dilokalisasi?

Cara paling sederhana untuk melakukan ini adalah dengan Enum.GetValue(dan mendukung penggunaan trip-tripping Enum.Parse). Sering juga layak untuk membangun TypeConverter, seperti yang disarankan Steve Mitcham, untuk mendukung pengikatan UI. (Tidak perlu untuk membangun TypeConverterketika Anda menggunakan lembar properti, yang merupakan salah satu hal yang menyenangkan tentang lembar properti. Meskipun tuan tahu mereka memiliki masalah sendiri.)

Secara umum, jika jawaban atas pertanyaan di atas menunjukkan bahwa tidak akan berhasil, langkah saya selanjutnya adalah membuat dan mengisi statis Dictionary<MyEnum, string>, atau mungkin a Dictionary<Type, Dictionary<int, string>>. Saya cenderung melewatkan langkah mendekorasi-kode-dengan-atribut sementara karena apa yang biasanya muncul selanjutnya adalah perlunya mengubah nilai-nilai ramah setelah penyebaran (sering, tetapi tidak selalu, karena lokalisasi).

Robert Rossney
sumber
7

Saya ingin memposting ini sebagai komentar untuk posting yang dikutip di bawah ini tetapi tidak bisa karena saya tidak punya cukup perwakilan - jadi tolong jangan turunkan suara. Kode berisi kesalahan dan saya ingin menunjukkan ini kepada individu yang mencoba menggunakan solusi ini:

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

seharusnya

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

Paula Bean
sumber
5

Varian saya

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

Kode terlihat agak jelek, tetapi penggunaan struct ini cukup presentatif.

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

Juga, saya pikir, jika banyak enum diperlukan, pembuatan kode (misalnya T4) dapat digunakan.

Razoomnick
sumber
4

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

lalu

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

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

DoSomethingWithTheAuth(auth.ToString());

Pilihan 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");
    }
}
Pablo Retyk
sumber
4

Jika Anda memikirkan masalah yang kami coba selesaikan, itu bukan enum yang kami butuhkan sama sekali. Kita membutuhkan objek yang memungkinkan sejumlah nilai dikaitkan dengan satu sama lain; dengan kata lain, untuk mendefinisikan kelas.

Pola enum yang aman untuk tipe Jakub Šturc adalah pilihan terbaik yang saya lihat di sini.

Lihat itu:

  • Ini memiliki konstruktor pribadi sehingga hanya kelas itu sendiri yang dapat menentukan nilai yang diizinkan.
  • Ini adalah kelas yang disegel sehingga nilai tidak dapat dimodifikasi melalui pewarisan.
  • Ini tipe-aman, memungkinkan metode Anda untuk hanya memerlukan jenis itu.
  • Tidak ada hit kinerja refleksi yang terjadi dengan mengakses nilai-nilai.
  • Dan yang terakhir, dapat dimodifikasi untuk mengaitkan lebih dari dua bidang secara bersamaan, misalnya Nama, Deskripsi, dan Nilai numerik.
Harvo
sumber
4

bagi saya, pendekatan pragmatis adalah kelas di dalam kelas, contoh:

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

Saya membuat kelas dasar untuk membuat enums bernilai string di .NET. Ini hanya satu file C # yang dapat Anda salin & tempel ke proyek Anda, atau instal melalui paket NuGet bernama StringEnum . GitHub Repo

  • Intellisense akan menyarankan nama enum jika kelas dianotasi dengan komentar xml <completitionlist>. (Bekerja di C # dan VB)

Demo Intellisense

  • Penggunaannya mirip dengan enum biasa:
///<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)

Instalasi:

  • Rekatkan kelas dasar StringEnum berikut ke proyek Anda. ( versi terbaru )
  • Atau instal paket StringEnum NuGet, yang didasarkan pada .Net Standard 1.0sehingga berjalan pada .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6, dll.
    /// <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/
            }
        }
    }
Gerardo Grignoli
sumber
3

Inilah cara lain untuk menyelesaikan tugas mengaitkan string dengan enum:

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

Metode ini disebut seperti ini:

public FormMain() {
    DATABASE dbEnum;

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

Anda dapat mengelompokkan enum terkait di struct mereka sendiri. Karena metode ini menggunakan tipe enum, Anda dapat menggunakan Intellisense untuk menampilkan daftar enum saat melakukan GetString()panggilan.

Anda secara opsional dapat menggunakan operator baru pada DATABASEstruct. Tidak menggunakannya berarti string Listtidak dialokasikan sampai GetString()panggilan pertama dilakukan.

Russ
sumber
3

Banyak jawaban bagus di sini tetapi dalam kasus saya tidak menyelesaikan apa yang saya inginkan dari "string enum", yaitu:

  1. Dapat digunakan dalam pernyataan sakelar mis. Sakelar (myEnum)
  2. Dapat digunakan dalam parameter fungsi misalnya foo (tipe myEnum)
  3. Dapat direferensikan misalnya myEnum.FirstElement
  4. Saya bisa menggunakan string mis. Foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 & 4 sebenarnya dapat diselesaikan dengan C # Typedef dari suatu string (karena string dapat diganti di c #)

3 dapat diselesaikan dengan string const statis. Jadi, jika Anda memiliki kebutuhan yang sama, ini adalah pendekatan paling sederhana:

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

}

Ini memungkinkan misalnya:

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

dan

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

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Di mana CreateType dapat dipanggil dengan string atau tipe. Namun downside adalah bahwa setiap string secara otomatis adalah enum yang valid , ini dapat dimodifikasi tetapi kemudian akan memerlukan semacam fungsi init ... atau mungkin membuat mereka secara eksplisit melemparkan internal?

Sekarang jika nilai int penting bagi Anda (mungkin untuk kecepatan perbandingan), Anda dapat menggunakan beberapa ide dari Jakub Šturc jawaban yang fantastis dan melakukan sesuatu yang sedikit gila, ini adalah tikaman saya untuk itu:

    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

}

tapi tentu saja "Jenis bob = 4;" akan menjadi tidak berarti kecuali Anda menginisialisasi mereka terlebih dahulu yang akan semacam mengalahkan intinya ...

Namun dalam teori, TypeA == TypeB akan lebih cepat ...

chrispepper1989
sumber
3

Jika saya memahami Anda dengan benar, Anda cukup menggunakan .ToString () untuk mengambil nama enum dari nilai (Anggap sudah dilemparkan sebagai Enum); Jika Anda memiliki int telanjang (katakanlah dari database atau sesuatu), Anda dapat terlebih dahulu melemparkannya ke enum. Kedua metode di bawah ini akan memberi Anda nama enum.

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

Perlu diingat, teknik kedua mengasumsikan Anda menggunakan int dan indeks Anda berdasarkan 1 (bukan berbasis 0). Fungsi GetNames juga cukup berat sebagai perbandingan, Anda menghasilkan seluruh array setiap kali dipanggil. Seperti yang Anda lihat pada teknik pertama, .ToString () sebenarnya disebut secara implisit. Kedua hal ini sudah disebutkan dalam jawaban tentu saja, saya hanya berusaha menjelaskan perbedaan di antara mereka.

Seluruh
sumber
3

pos lama tapi ...

Jawabannya sebenarnya sangat sederhana. Gunakan fungsi Enum.ToString ()

Ada 6 kelebihan fungsi ini, Anda dapat menggunakan Enum.Tostring ("F") atau Enum.ToString () untuk mengembalikan nilai string. Tidak perlu repot dengan hal lain. Ini Demo yang berfungsi

Perhatikan bahwa solusi ini mungkin tidak berfungsi untuk semua kompiler ( demo ini tidak berfungsi seperti yang diharapkan ) tetapi setidaknya ini berfungsi untuk kompiler terbaru.

Hammad Khan
sumber
2

Nah, setelah membaca semua hal di atas saya merasa bahwa orang-orang memiliki masalah rumit mengubah enumerator menjadi string. Saya menyukai gagasan memiliki atribut di atas bidang yang disebutkan tetapi saya pikir atribut tersebut terutama digunakan untuk Meta-data, tetapi dalam kasus Anda, saya pikir semua yang Anda butuhkan adalah semacam pelokalan.

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

Sekarang jika kita mencoba memanggil metode di atas kita dapat memanggilnya dengan cara ini

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

Yang perlu Anda lakukan hanyalah membuat file sumber yang berisi semua nilai enumerator dan string yang sesuai

Nama Sumber Daya Nilai Sumber Daya
Color_Red My String Color in Red
Color_Blue Blueeey
Color_Green Hulk Color

Apa yang sebenarnya sangat baik tentang itu adalah bahwa itu akan sangat membantu jika Anda memerlukan aplikasi Anda untuk dilokalisasi, karena yang perlu Anda lakukan hanyalah membuat file sumber daya lain dengan bahasa baru Anda! dan Voe-la!

Bormagi
sumber
1

Ketika saya berada dalam situasi seperti itu saya mengusulkan solusi di bawah ini.

Dan sebagai kelas konsumsi Anda bisa memilikinya

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

    }
}

Dan menggunakan kamus dua arah: Berdasarkan ini ( https://stackoverflow.com/a/255638/986160 ) dengan asumsi bahwa kunci akan dikaitkan dengan nilai tunggal dalam kamus dan mirip dengan ( https://stackoverflow.com/a / 255630/986160 ) tetapi sedikit lebih elegan. Kamus ini juga dapat dihitung dan Anda dapat bolak-balik dari int ke string. Anda juga tidak harus memiliki string apa pun di basis kode Anda dengan pengecualian kelas ini.

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();
        }
    }
}
Michail Michailidis
sumber
1

Untuk set string enum yang lebih besar, contoh-contoh yang tercantum dapat menjadi melelahkan. Jika Anda ingin daftar kode status, atau daftar enum berbasis string lainnya, sistem atribut menjengkelkan untuk digunakan, dan kelas statis dengan instance sendiri menjengkelkan untuk dikonfigurasi. Untuk solusi saya sendiri, saya menggunakan T4 templating untuk membuatnya lebih mudah untuk memiliki enum yang didukung string. Hasilnya mirip dengan cara kerja kelas HttpMethod.

Anda bisa menggunakannya seperti ini:

    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; 

Anda mulai dengan file 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."
    };
}
#>

Kemudian, Anda menambahkan file StringEnum.ttinclude Anda.

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

Akhirnya, Anda mengkompilasi ulang file Enum.tt Anda dan hasilnya terlihat seperti ini:

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