Buat metode Generik membatasi T ke Enum

1189

Saya membangun fungsi untuk memperluas Enum.Parsekonsep itu

  • Mengizinkan nilai default diurai jika nilai Enum tidak ditemukan
  • Tidak sensitif huruf

Jadi saya menulis yang berikut:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Saya mendapatkan Galat Kendala tidak boleh kelas khusus System.Enum.

Cukup adil, tetapi apakah ada solusi untuk memungkinkan Generic Enum, atau saya harus meniru Parsefungsi dan meneruskan tipe sebagai atribut, yang memaksa persyaratan tinju jelek ke kode Anda.

Sunting Semua saran di bawah ini sangat kami hargai, terima kasih.

Telah diselesaikan (Saya telah meninggalkan loop untuk mempertahankan ketidakpekaan huruf - Saya menggunakan ini saat mem-parsing XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

EDIT: (16 Feb 2015) Julien Lebosquain baru-baru ini memposting solusi generik aman jenis-kompiler yang ditegakkan dalam MSIL atau F # di bawah ini, yang layak untuk dilihat, dan upvote. Saya akan menghapus hasil edit ini jika solusinya menggelembung di atas halaman.

John
sumber
10
Mungkin Anda harus menggunakan ToUpperInvariant () alih-alih ToLower () ...
Max Galkin
31
@ Shimmy: Segera setelah Anda melewati tipe nilai ke metode ekstensi, Anda sedang mengerjakan salinannya, jadi Anda tidak dapat mengubah statusnya.
Garo Yeriazarian
4
Tahu itu adalah utas lama, tidak tahu apakah mereka mengubah hal-hal, tetapi metode ekstensi berfungsi dengan baik untuk tipe nilai, tentu mereka tidak selalu masuk akal, tetapi saya telah menggunakan "TimeSpan Seconds statis publik (int x ini) { kembalikan TimeSpan.FromSeconds (x);} "untuk mengaktifkan sintaksis" Tunggu.Untuk (5.Seconds ()) ... "
Jens
6
Menyadari ini bukan bagian dari pertanyaan, tapi Anda bisa meningkatkan logika foreach loop Anda dengan menggunakan String.Equals dengan StringComparison.InvariantCultureIgnoreCase
Firestrand

Jawaban:

1006

Karena EnumType mengimplementasikanIConvertible antarmuka, implementasi yang lebih baik harus seperti ini:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Ini masih akan mengizinkan berlalunya jenis nilai implementasi IConvertible. Kemungkinannya jarang.

Vivek
sumber
2
Generik tersedia sejak .NET 2.0. Karenanya mereka tersedia dalam vb 2005 juga.
Vivek
46
Nah, buat lebih dibatasi lagi, jika Anda memilih untuk turun jalan ini ... gunakan "kelas TestClass <T> di mana T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde
106
Saran lain adalah mendefinisikan tipe generik dengan pengenal TEnum. Dengan demikian: TEnum publik GetEnumFromString <TEnum> (nilai string) di mana TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa
11
Anda tidak mendapatkan banyak dengan memasukkan antarmuka lain karena hampir semua tipe nilai bawaan menerapkan semua antarmuka tersebut. Hal ini terutama berlaku untuk kendala pada metode ekstensi generik, yang sangat berguna untuk beroperasi pada enum, kecuali kenyataan bahwa metode ekstensi itu seperti virus yang menginfeksi semua objek Anda. IConvertable setidaknya mempersempitnya sedikit.
russbishop
2
@ Samam: Ketika Anda memposting, utas ini adalah apa, berusia 6 setengah tahun, dan Anda benar, tidak ada waktu kompilasi yang memeriksa salah satu jawaban. Kemudian hanya 3 hari kemudian, setelah 6 tahun, Anda mendapatkan keinginan Anda - lihat posting Julien Lebosquain di bawah ini.
David I. McIntosh
663

Fitur ini akhirnya didukung di C # 7.3!

Cuplikan berikut (dari sampel dotnet ) menunjukkan caranya:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Pastikan untuk mengatur versi bahasa Anda dalam proyek C # Anda ke versi 7.3.


Jawaban Asli di bawah ini:

Saya terlambat ke pertandingan, tetapi saya menganggapnya sebagai tantangan untuk melihat bagaimana itu bisa dilakukan. Itu tidak mungkin dalam C # (atau VB.NET, tetapi gulir ke bawah untuk F #), tetapi dimungkinkan dalam MSIL. Saya menulis .... hal kecil ini

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Yang menghasilkan fungsi yang akan terlihat seperti ini, jika itu valid C #:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Kemudian dengan kode C # berikut:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

Sayangnya, ini berarti bagian kode Anda ini ditulis dalam MSIL dan bukan C #, dengan satu-satunya manfaat tambahan adalah Anda dapat membatasi metode ini System.Enum. Ini juga agak menyebalkan, karena akan dikompilasi menjadi rakitan terpisah. Namun, itu tidak berarti Anda harus menggunakannya dengan cara itu.

Dengan menghapus garis .assembly MyThing{} dan memohon ilasm sebagai berikut:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

Anda mendapatkan netmodule alih-alih perakitan.

Sayangnya, VS2010 (dan sebelumnya, jelas) tidak mendukung penambahan referensi netmodule, yang berarti Anda harus meninggalkannya dalam 2 rakitan terpisah ketika Anda sedang melakukan debug. Satu-satunya cara Anda dapat menambahkannya sebagai bagian dari perakitan Anda adalah dengan menjalankan csc.exe sendiri menggunakan /addmodule:{files}argumen baris perintah. Tidak mungkin terlalu menyakitkan dalam skrip MSBuild. Tentu saja, jika Anda berani atau bodoh, Anda dapat menjalankan csc sendiri secara manual setiap kali. Dan itu tentu saja menjadi lebih rumit karena banyak majelis membutuhkan akses ke sana.

Jadi, BISA dilakukan di .Net. Apakah ini sepadan dengan usaha ekstra? Um, well, kurasa aku akan membiarkanmu memutuskan yang itu.


F # Solusi sebagai alternatif

Kredit Ekstra: Ternyata pembatasan generik enummungkin dilakukan pada setidaknya satu bahasa .NET selain MSIL: F #.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

Yang ini lebih mudah dipertahankan karena ini adalah bahasa yang terkenal dengan dukungan Visual Studio IDE penuh, tetapi Anda masih memerlukan proyek terpisah dalam solusi Anda untuk itu. Namun, secara alami menghasilkan IL yang sangat berbeda (kode ini sangat berbeda) dan bergantung padaFSharp.Core perpustakaan, yang, seperti perpustakaan eksternal lainnya, perlu menjadi bagian dari distribusi Anda.

Inilah cara Anda dapat menggunakannya (pada dasarnya sama dengan solusi MSIL), dan untuk menunjukkan bahwa itu gagal dengan benar pada struct yang identik:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);
Christopher Currens
sumber
67
Ya, sangat hardcore. Saya sangat menghormati seseorang yang dapat kode dalam IL, dan tahu bagaimana fitur-fitur yang didukung pada tingkat bahasa yang lebih tinggi - tingkat yang banyak dari kita masih melihat tingkat rendah di bawah aplikasi, aturan bisnis, UI, perpustakaan komponen, dll. .
TonyG
13
Yang saya benar-benar ingin tahu adalah mengapa tim C # belum mulai mengizinkan ini, karena sudah didukung oleh MSIL.
MgSam
25
@MgSam - Dari Eric Lippert :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Christopher Currens
5
@LordofScripts: Saya pikir alasannya adalah bahwa sejak kelas yang membatasi suatu Tuntuk System.Enumtidak akan mampu melakukan semua hal dengan Tyang orang mungkin harapkan, penulis C # pikir mereka mungkin juga melarang sama sekali. Saya menganggap keputusan ini tidak menguntungkan, karena C # hanya mengabaikan penanganan khusus System.Enumkendala, akan mungkin untuk menulis HasAnyFlags<T>(this T it, T other)metode ekstensi yang urutan besarnya lebih cepat daripada Enum.HasFlag(Enum)jenis yang memeriksa argumennya.
supercat
9
Saya tidak berpikir saya pernah punya proyek di mana saya tidak berakhir di sini. C # 6 adalah 110% gula sintaksis dan INI tidak masuk? Potong omong kosong.
Michael Blackburn
214

C # ≥ 7.3

Dimulai dengan C # 7.3 (tersedia dengan Visual Studio 2017 ≥ v15.7), kode ini sekarang benar-benar valid:

public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, Enum
{
 ...
}

C # ≤ 7.2

Anda dapat memiliki kompiler enum constraint yang diberlakukan dengan menyalahgunakan pewarisan kendala. Kode berikut menentukan a classdan sekaligus structkendala:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

    public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Pemakaian:

EnumUtils.Parse<SomeEnum>("value");

Catatan: ini secara khusus dinyatakan dalam spesifikasi bahasa C # 5.0:

Jika tipe parameter S tergantung pada tipe parameter T maka: [...] Adalah valid untuk S memiliki batasan tipe nilai dan T memiliki batasan tipe referensi. Secara efektif ini membatasi T untuk tipe System.Object, System.ValueType, System.Enum, dan semua tipe antarmuka.

Julien Lebosquain
sumber
7
@ DavidI.McIntosh EnumClassUtils<System.Enum>cukup untuk membatasi T untuk semua System.Enumjenis turunan. structdi Parsekemudian membatasi lebih jauh ke tipe enum nyata. Anda perlu membatasi hingga Enumtitik tertentu. Untuk melakukannya, kelas Anda harus disarangkan. Lihat gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain
7
Untuk lebih jelasnya, komentar saya "tidak menyenangkan" bukanlah komentar atas solusi Anda - ini benar-benar hack yang indah. Hanya "tidak menyenangkan" MS memaksa kita untuk menggunakan hack yang berbelit-belit.
David I. McIntosh
2
Apakah ada cara untuk bekerja ini agar dapat digunakan untuk metode ekstensi?
Mord Zuber
3
Apa yang menjadi where TClass : classkendala di sini?
tsemer
2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm
30

Edit

Pertanyaan itu sekarang luar biasa dijawab oleh Julien Lebosquain . Saya juga ingin memperluas jawabannya dengan ignoreCase, defaultValuedan argumen opsional, sambil menambahkan TryParsedan ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Contoh penggunaan:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

Tua

Perbaikan lama saya pada jawaban Vivek dengan menggunakan komentar dan perkembangan 'baru':

  • menggunakan TEnum untuk kejelasan bagi pengguna
  • tambahkan lebih banyak antarmuka-kendala untuk pemeriksaan kendala tambahan
  • biarkan TryParsepeganganignoreCase dengan parameter yang ada (diperkenalkan pada VS2010 / .Net 4)
  • opsional menggunakan generik defaultnilai (diperkenalkan di VS2005 / Net 2)
  • gunakan argumen opsional (diperkenalkan pada VS2010 / .Net 4) dengan nilai default, untuk defaultValuedanignoreCase

yang menghasilkan:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}
Yahoo Serius
sumber
18

Anda bisa mendefinisikan konstruktor statis untuk kelas yang akan memeriksa bahwa tipe T adalah enum dan melemparkan pengecualian jika tidak. Ini adalah metode yang disebutkan oleh Jeffery Richter dalam bukunya CLR via C #.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Kemudian dalam metode parse, Anda bisa menggunakan Enum.Parse (typeof (T), input, true) untuk mengkonversi dari string ke enum. Parameter benar terakhir adalah untuk mengabaikan kasus input.

Karg
sumber
1
Ini adalah pilihan yang baik untuk kelas generik - tetapi tentu saja, itu tidak membantu untuk metode generik.
McGarnagle
Juga, ini juga tidak diberlakukan pada waktu kompilasi, Anda hanya akan tahu Anda memberikan non Enum Tketika konstruktor dijalankan. Meskipun ini jauh lebih bagus daripada menunggu contoh konstruktor.
jrh
15

Juga harus dipertimbangkan bahwa sejak pelepasan C # 7.3 menggunakan batasan Enum didukung di luar kotak tanpa harus melakukan pemeriksaan dan hal-hal tambahan.

Jadi terus maju dan mengingat Anda telah mengubah versi bahasa proyek Anda menjadi C # 7.3 kode berikut akan berfungsi dengan baik:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

Jika Anda tidak tahu cara mengubah versi bahasa ke C # 7.3 lihat tangkapan layar berikut: masukkan deskripsi gambar di sini

EDIT 1 - Diperlukan Versi Visual Studio dan mempertimbangkan ReSharper

Agar Visual Studio mengenali sintaks baru, Anda memerlukan setidaknya versi 15.7. Anda dapat menemukan yang juga disebutkan dalam catatan rilis Microsoft, lihat Visual Studio 2017 15.7 Catatan Rilis . Terima kasih @MohamedElshawaf untuk menunjukkan pertanyaan yang valid ini.

Tolong juga perhatikan bahwa dalam kasus saya ReSharper 2018.1 saat penulisan EDIT ini belum mendukung C # 7.3. Setelah ReSharper mengaktifkannya menyoroti kendala Enum sebagai kesalahan yang memberitahukan saya Tidak dapat menggunakan 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'objek' sebagai batasan parameter tipe . ReSharper menyarankan sebagai perbaikan cepat untuk menghapus batasan 'Enum' dari tipe paramter T metode

Namun, jika Anda mematikan ReSharper sementara di bawah Tools -> Options -> ReSharper Ultimate -> General Anda akan melihat bahwa sintaksinya baik-baik saja mengingat Anda menggunakan VS 15.7 atau lebih tinggi dan C # 7.3 atau lebih tinggi.

baumgarb
sumber
1
Apa versi VS yang Anda gunakan?
mshwf
1
@MohamedElshawaf Saya percaya ini versi 15.7 yang berisi dukungan untuk C # 7.3
Patrick Roberts
1
Saya pikir lebih baik untuk menulis where T : struct, Enum, untuk menghindari melewatkan System.Enumdirinya sebagai parameter tipe.
Mariusz Pawelski
Seperti @MariuszPawelski yang saya tulis struct, Enum. Dasar pemikiran saya dijelaskan dalam jawaban dan komentar di sini .
Stephen Kennedy
Info ReSharper sangat membantu saya. Catatan versi pratinjau terbaru mendukung fitur ini.
DalSoft
11

Saya memodifikasi sampel dengan dimarzionist. Versi ini hanya akan bekerja dengan Enums dan tidak membiarkan struct melewati.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}
Bivoauc
sumber
13
Saya tidak akan mengembalikan nilai default pada kegagalan; Saya akan membiarkan pengecualian menyebar (seperti halnya dengan Enum.Parse). Sebagai gantinya, gunakan TryParse mengembalikan bool dan mengembalikan hasilnya menggunakan param.
Mark Simpson
1
OP ingin itu menjadi case-insensitive, ini bukan.
Konrad Morawski
9

Saya mencoba sedikit memperbaiki kode:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}
Martin
sumber
1
Ini lebih baik daripada jawaban yang diterima karena itu memungkinkan Anda untuk memanggil defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)meskipun Anda tidak tahu jenis enum apa itu, hanya saja objeknya adalah enum.
styfle
1
Pemeriksaan di muka IsDefinedakan merusak ketidaksensitifan kasus. Tidak seperti Parse, IsDefinedtidak memiliki ignoreCaseargumen, dan MSDN mengatakan itu hanya cocok dengan kasus yang tepat .
Nyerguds
5

Saya memang memiliki persyaratan khusus di mana saya harus menggunakan enum dengan teks yang terkait dengan nilai enum. Sebagai contoh ketika saya menggunakan enum untuk menentukan jenis kesalahan itu diperlukan untuk menjelaskan detail kesalahan.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}
Sunny Rajwadi
sumber
4

Semoga ini bermanfaat:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}
dimarzionis
sumber
1
Jika Anda membutuhkan ketidakpekaan kasus, cukup ganti return (TValue)Enum.Parse(typeof (TValue), value);denganreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos
3

Cukup menarik, ternyata ini dimungkinkan di bahasa lain (Managed C ++, IL langsung).

Kutipan:

... Kedua kendala sebenarnya menghasilkan IL yang valid dan juga dapat dikonsumsi oleh C # jika ditulis dalam bahasa lain (Anda dapat mendeklarasikan kendala tersebut dalam C ++ yang dikelola atau dalam IL).

Siapa tahu

Andrew Backer
sumber
2
Ekstensi Terkelola untuk C ++ tidak memiliki dukungan APAPUN untuk obat generik, saya pikir maksud Anda C ++ / CLI.
Ben Voigt
3

Ini adalah pendapat saya. Digabungkan dari jawaban dan MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

Sumber MSDN

KarmaEDV
sumber
2
Ini tidak masuk akal. Jika TEnumsebenarnya adalah tipe Enum tetapi textadalah string kosong maka Anda mendapatkan ArgumentExceptionpepatah "TEnum harus menjadi tipe Enum" meskipun itu.
Nick
3

Jawaban yang ada benar pada C # <= 7.2. Namun, ada permintaan fitur bahasa C # (terkait dengan permintaan fitur corefx ) untuk memungkinkan hal berikut;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

Pada saat penulisan, fitur ini adalah "Dalam diskusi" di Pertemuan Pengembangan Bahasa.

EDIT

Sesuai info nawfal , ini sedang diperkenalkan di C # 7.3 .

DiskJunky
sumber
1
Diskusi yang menarik di sana, terima kasih. Belum ada yang ditetapkan di atas batu (sampai sekarang)
johnc
1
@ johnc, sangat benar tetapi patut dicatat dan ini adalah fitur yang sering ditanyakan. Peluang yang adil untuknya datang.
DiskJunky
1
Ini akan tersedia dalam C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/… . :)
nawfal
1

Saya selalu menyukai ini (Anda dapat memodifikasi sesuai kebutuhan):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}
Jeff
sumber
1

Saya menyukai solusi Christopher Currens menggunakan IL tetapi bagi mereka yang tidak ingin berurusan dengan bisnis rumit termasuk MSIL ke dalam proses pembangunan mereka, saya menulis fungsi serupa di C #.

Harap perhatikan bahwa Anda tidak dapat menggunakan pembatasan umum seperti where T : Enum karena Enum adalah tipe khusus. Karena itu saya harus memeriksa apakah tipe generik yang diberikan benar-benar enum.

Fungsi saya adalah:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}
ahli
sumber
1

Saya telah merangkum solusi Vivek ke dalam kelas utilitas yang dapat Anda gunakan kembali. Harap dicatat bahwa Anda masih harus mendefinisikan batasan tipe "di mana T: struct, IConvertible" pada tipe Anda.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}
lebih lugu
sumber
1

Saya membuat Metode ekstensi to get integer value from enum lihat implementasi metode

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

ini adalah penggunaan

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
Basheer AL-MOMANI
sumber
Meskipun mungkin berhasil, hampir tidak ada relevansinya dengan pertanyaan.
quetzalcoatl
1

Sebagaimana dinyatakan dalam jawaban lain sebelumnya; sementara ini tidak dapat diekspresikan dalam kode-sumber itu sebenarnya dapat dilakukan pada IL Level. @Christopher Currens menjawab menunjukkan bagaimana IL lakukan untuk itu.

Dengan Fody 's Add-In ExtraConstraints.Fody ada cara yang sangat sederhana, lengkap dengan build-tooling, untuk mencapai ini. Cukup tambahkan paket nuget mereka ( Fody, ExtraConstraints.Fody) ke proyek Anda dan tambahkan kendala sebagai berikut (Kutipan dari Readme of ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

dan Fody akan menambahkan IL yang diperlukan agar kendala hadir. Perhatikan juga fitur tambahan dari delegasi yang membatasi:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Mengenai Enum, Anda mungkin juga ingin mencatat Enums.NET yang sangat menarik .

BatteryBackupUnit
sumber
1

Ini implementasi saya. Pada dasarnya, Anda dapat mengatur atribut apa saja dan berfungsi.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }
Cubelaster
sumber
0

Jika boleh menggunakan casting langsung setelah itu, saya kira Anda dapat menggunakan System.Enumkelas dasar dalam metode Anda, di mana pun diperlukan. Anda hanya perlu mengganti parameter tipe dengan hati-hati. Jadi implementasi metode akan seperti:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Maka Anda bisa menggunakannya seperti:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
uluorta
sumber
penggunaan Enum.ToObject()akan menghasilkan hasil yang lebih fleksibel. Selain itu, Anda dapat melakukan perbandingan string tanpa sensitivitas case yang akan meniadakan kebutuhan untuk memanggilToLower()
DiskJunky
-6

Hanya untuk kelengkapan, berikut ini adalah solusi Java. Saya yakin hal yang sama bisa dilakukan di C # juga. Ini menghindari keharusan untuk menentukan jenis di mana saja dalam kode - sebagai gantinya, Anda menentukannya di string yang Anda coba parsing.

Masalahnya adalah bahwa tidak ada cara untuk mengetahui enumerasi mana yang cocok dengan String - jadi jawabannya adalah untuk menyelesaikan masalah itu.

Alih-alih hanya menerima nilai string, terima String yang memiliki enumerasi dan nilai dalam bentuk "enumeration.value". Kode kerjanya di bawah ini - membutuhkan Java 1.8 atau yang lebih baru. Ini juga akan membuat XML lebih tepat karena Anda akan melihat sesuatu seperti color = "Color.red" daripada hanya color = "red".

Anda akan memanggil metode acceptEnumeratedValue () dengan string yang berisi nama nilai titik enum nama.

Metode mengembalikan nilai enumerasi formal.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Rodney P. Barbati
sumber