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