Tính năng này cuối cùng được hỗ trợ trong C # 7.3!
Đoạn mã sau (từ các mẫu dotnet ) giải thích cách:
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;
}
Đảm bảo đặt phiên bản ngôn ngữ của bạn trong dự án C # của bạn thành phiên bản 7.3.
Câu trả lời gốc dưới đây:
Tôi đến trễ trò chơi, nhưng tôi coi đó là một thử thách để xem làm thế nào nó có thể được thực hiện. Không thể có trong C # (hoặc VB.NET, nhưng cuộn xuống cho F #), nhưng có thể có trong MSIL. Tôi đã viết một chút .... điều này
// 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
}
}
Mà tạo ra một chức năng mà sẽ trông như thế này, nếu nó là hợp lệ C #:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
Sau đó, với mã C # sau:
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
}
Thật không may, điều này có nghĩa là có phần mã này được viết bằng MSIL thay vì C #, với lợi ích duy nhất được thêm vào là bạn có thể hạn chế phương pháp này bằng cách System.Enum
. Nó cũng giống như một người lập dị, bởi vì nó được biên dịch thành một hội đồng riêng biệt. Tuy nhiên, điều đó không có nghĩa là bạn phải triển khai theo cách đó.
Bằng cách loại bỏ dòng .assembly MyThing{}
và gọi ilasm như sau:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
bạn nhận được một netmodule thay vì lắp ráp.
Thật không may, VS2010 (và trước đó, rõ ràng) không hỗ trợ thêm các tham chiếu netmodule, điều đó có nghĩa là bạn phải để nó thành 2 cụm riêng biệt khi bạn gỡ lỗi. Cách duy nhất bạn có thể thêm chúng như là một phần của hội đồng của bạn là tự chạy csc.exe bằng cách sử dụng /addmodule:{files}
đối số dòng lệnh. Nó sẽ không quá đau đớn trong một kịch bản MSBuild. Tất nhiên, nếu bạn dũng cảm hoặc ngu ngốc, bạn có thể tự chạy csc bằng tay mỗi lần. Và nó chắc chắn trở nên phức tạp hơn khi nhiều hội đồng cần truy cập vào nó.
Vì vậy, nó có thể được thực hiện trong .Net. Có đáng nỗ lực thêm? Ừm, tôi đoán tôi sẽ để bạn quyết định cái đó.
Giải pháp F # thay thế
Tín dụng bổ sung: Hóa ra có thể hạn chế chung về enum
ít nhất một ngôn ngữ .NET khác ngoài 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
Ngôn ngữ này dễ bảo trì hơn vì đây là ngôn ngữ nổi tiếng với sự hỗ trợ đầy đủ của Visual Studio IDE, nhưng bạn vẫn cần một dự án riêng trong giải pháp của mình cho nó. Tuy nhiên, một cách tự nhiên sản xuất khác nhau đáng kể IL (mã là rất khác nhau) và nó dựa trên các FSharp.Core
thư viện, trong đó, giống như bất kỳ thư viện bên ngoài khác, cần phải trở thành một phần của phân phối của bạn.
Đây là cách bạn có thể sử dụng nó (về cơ bản giống như giải pháp MSIL) và để chỉ ra rằng nó thất bại chính xác trên các cấu trúc đồng nghĩa khác:
// 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);