Tạo phương thức Chung ràng buộc T thành Enum


1189

Tôi đang xây dựng một chức năng để mở rộng Enum.Parsekhái niệm rằng

  • Cho phép phân tích giá trị mặc định trong trường hợp không tìm thấy giá trị Enum
  • Là trường hợp không nhạy cảm

Vì vậy, tôi đã viết như sau:

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

Tôi nhận được một ràng buộc lỗi không thể là lớp đặc biệt System.Enum.

Đủ công bằng, nhưng có một cách giải quyết để cho phép một Enum chung, hoặc tôi sẽ phải bắt chước Parsechức năng và chuyển một loại như một thuộc tính, điều này buộc yêu cầu quyền anh xấu xí vào mã của bạn.

EDIT Tất cả các đề xuất dưới đây đã được đánh giá rất cao, cảm ơn.

Đã giải quyết (Tôi đã rời khỏi vòng lặp để duy trì tình trạng không nhạy cảm - Tôi đang sử dụng điều này khi phân tích cú pháp 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: (ngày 16 tháng 2 năm 2015) Julien Lebosquain gần đây đã đăng một trình biên dịch thực thi giải pháp chung an toàn loại trong MSIL hoặc F # bên dưới, rất đáng để xem và upvote. Tôi sẽ xóa chỉnh sửa này nếu giải pháp bong bóng hơn nữa trên trang.


10
Có lẽ bạn nên sử dụng ToUpperInvariant () thay vì ToLower () ...
Max Galkin

31
@Shimmy: Ngay sau khi bạn chuyển một loại giá trị cho phương thức tiện ích mở rộng, bạn đang làm việc trên một bản sao của nó, vì vậy bạn không thể thay đổi trạng thái của nó.
Garo Yeriazarian

4
Biết đó là một chủ đề cũ, không biết họ có thay đổi gì không, nhưng các phương thức mở rộng hoạt động tốt cho các loại giá trị, chắc chắn rằng chúng có thể không phải lúc nào cũng có ý nghĩa, nhưng tôi đã sử dụng "TimeSpan giây tĩnh (int x) { return TimeSpan.FromSeconds (x);} "để kích hoạt cú pháp của" Wait.For (5.Seconds ()) ... "
Jens

6
Nhận ra đây không phải là một phần của câu hỏi, nhưng bạn có thể cải thiện logic vòng lặp foreach của mình bằng cách sử dụng String.Equals với StringComparison.InvariantCARMIgnoreCase
Firestrand

Câu trả lời:


1006

EnumType thực hiện IConvertiblegiao diện, nên việc triển khai tốt hơn sẽ giống như thế này:

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

   //...
}

Điều này vẫn sẽ cho phép vượt qua các loại giá trị thực hiện IConvertible. Cơ hội là rất hiếm mặc dù.


2
Generics có sẵn kể từ .NET 2.0. Do đó, chúng có sẵn trong vb 2005.
Vivek

46
Chà, làm cho nó thậm chí còn bị ràng buộc nhiều hơn nữa, nếu bạn chọn đi xuống con đường này ... hãy sử dụng "lớp TestClass <T> trong đó T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde

106
Một đề nghị khác là xác định loại chung với định danh TEnum. Do đó: TEnum GetEnumFromString <TEnum> (giá trị chuỗi) trong đó TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa

11
Bạn không đạt được nhiều bằng cách bao gồm các giao diện khác vì hầu hết tất cả các loại giá trị tích hợp đều thực hiện tất cả các giao diện đó. Điều này đặc biệt đúng đối với các ràng buộc đối với một phương thức mở rộng chung, cực kỳ tiện lợi để vận hành trên enum, ngoại trừ thực tế là các phương thức mở rộng đó giống như một vi-rút lây nhiễm tất cả các đối tượng của bạn. IConvertable ít nhất thu hẹp nó xuống một chút.
Nga

2
@SamIam: Khi bạn đăng, chủ đề này là gì, 6 tuổi rưỡi, và bạn đã đúng, không kiểm tra thời gian biên dịch trong bất kỳ câu trả lời nào. Sau đó chỉ 3 ngày, sau 6 năm, bạn đã có được điều ước của mình - xem cách đăng bài của Julien Lebosquain bên dưới.
David I. McIntosh

663

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ã rất khác nhau) và nó dựa trên các FSharp.Corethư 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);

67
Vâng, rất khó tính. Tôi rất tôn trọng một người có thể viết mã trong IL biết các tính năng được hỗ trợ ở cấp độ ngôn ngữ cao hơn như thế nào - mức độ mà nhiều người trong chúng ta vẫn thấy là cấp độ thấp trong các ứng dụng, quy tắc kinh doanh, giao diện người dùng, thư viện thành phần, v.v. .
TonyG

13
Điều tôi thực sự muốn biết là tại sao nhóm C # chưa bắt đầu cho phép điều này vì nó đã được MSIL hỗ trợ.
MgSam

25
@MgSam - Từ 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: Tôi nghĩ lý do là rằng kể từ khi một lớp học vốn làm hạn chế một Tđể System.Enumsẽ không thể làm tất cả những gì có Tmà mọi người mong đợi, các tác giả của C # figured họ cũng có thể cấm nó hoàn toàn. Tôi coi quyết định này là không may, vì C # đơn giản đã bỏ qua mọi xử lý đặc biệt của các System.Enumràng buộc, nên có thể viết một HasAnyFlags<T>(this T it, T other)phương thức mở rộng nhanh hơn Enum.HasFlag(Enum)và kiểm tra các đối số của nó.
supercat

9
Tôi không nghĩ rằng tôi đã từng có một dự án mà tôi đã không kết thúc ở đây. C # 6 là 110% đường cú pháp và NÀY không vào được? Cắt tào lao.
Michael Blackburn

214

C # ≥ 7.3

Bắt đầu với C # 7.3 (có sẵn với Visual Studio 2017 v15.7), mã này hiện hoàn toàn hợp lệ:

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

C # ≤ 7.2

Bạn có thể có một trình biên dịch thực sự thực thi ràng buộc enum bằng cách lạm dụng kế thừa ràng buộc. Đoạn mã sau chỉ định cả a classstructràng buộc cùng một lúc:

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

Sử dụng:

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

Lưu ý: điều này được quy định cụ thể trong đặc tả ngôn ngữ C # 5.0:

Nếu tham số loại S phụ thuộc vào tham số loại T thì: [...] S có giá trị ràng buộc loại giá trị và T có ràng buộc loại tham chiếu. Thực tế, điều này giới hạn T đối với các loại System.Object, System.ValueType, System.Enum và bất kỳ loại giao diện nào.


7
@ DavidI.McIntosh EnumClassUtils<System.Enum>là đủ để hạn chế T đối với bất kỳ System.Enumvà bất kỳ loại dẫn xuất nào. structtrên Parsesau đó hạn chế nó hơn nữa để một kiểu enum thực. Bạn cần hạn chế đến Enummột lúc nào đó. Để làm như vậy, lớp của bạn phải được lồng. Xem gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain

7
Nói rõ hơn, bình luận của tôi "không dễ chịu" không phải là một bình luận về giải pháp của bạn - nó thực sự là một bản hack tuyệt đẹp. Chỉ "không dễ chịu" là MS buộc chúng ta phải sử dụng một bản hack phức tạp như vậy.
David I. McIntosh

2
Có cách nào để làm việc này cũng có thể sử dụng được cho các phương thức mở rộng không?
Mord Zuber

3
Những gì where TClass : classràng buộc đạt được ở đây?
tsTable

2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm

30

Biên tập

Câu hỏi bây giờ đã được trả lời tuyệt vời bởi Julien Lebosquain . Tôi cũng muốn mở rộng câu trả lời của anh ấy với ignoreCase, defaultValuevà các đối số tùy chọn, trong khi thêm TryParseParseOrDefault.

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
}

Ví dụ về việc sử dụng:

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

Những cải tiến cũ của tôi về câu trả lời của Vivek bằng cách sử dụng các nhận xét và phát triển 'mới':

  • sử dụng TEnum cho rõ ràng cho người dùng
  • thêm nhiều ràng buộc giao diện để kiểm tra ràng buộc bổ sung
  • hãy TryParsexử lý ignoreCasevới tham số hiện có (được giới thiệu trong VS2010 / .Net 4)
  • tùy chọn sử dụng defaultgiá trị chung (được giới thiệu trong VS2005 / .Net 2)
  • sử dụng các đối số tùy chọn (được giới thiệu trong VS2010 / .Net 4) với các giá trị mặc định, cho defaultValueignoreCase

dẫn đến:

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

18

Bạn có thể định nghĩa một hàm tạo tĩnh cho lớp sẽ kiểm tra xem kiểu T có phải là enum không và ném ngoại lệ nếu không. Đây là phương pháp được Jeffery Richter đề cập trong cuốn sách CLR của mình thông qua C #.

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

Sau đó, trong phương thức phân tích cú pháp, bạn chỉ có thể sử dụng Enum.Pude (typeof (T), input, true) để chuyển đổi từ chuỗi sang enum. Tham số đúng cuối cùng là bỏ qua trường hợp đầu vào.


1
Đây là một lựa chọn tốt cho các lớp chung - nhưng tất nhiên, nó không giúp ích cho các phương thức chung.
McGarnagle 9/2/2015

Ngoài ra, điều này cũng không được thi hành tại thời điểm biên dịch, bạn sẽ chỉ biết bạn đã cung cấp một lệnh không Enum Tkhi hàm tạo thực thi. Mặc dù điều này là tốt hơn nhiều so với chờ đợi một nhà xây dựng cá thể.
jrh

15

Cũng cần xem xét rằng kể từ khi phát hành C # 7.3 sử dụng các ràng buộc Enum được hỗ trợ ngoài luồng mà không phải thực hiện kiểm tra bổ sung và công cụ.

Vì vậy, hãy tiếp tục và cho bạn đã thay đổi phiên bản ngôn ngữ của dự án của bạn thành C # 7.3, đoạn mã sau sẽ hoạt động hoàn toàn tốt:

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

Trong trường hợp bạn không biết cách thay đổi phiên bản ngôn ngữ thành C # 7.3, hãy xem ảnh chụp màn hình sau: nhập mô tả hình ảnh ở đây

EDIT 1 - Phiên bản Visual Studio bắt buộc và xem xét ReSharper

Để Visual Studio nhận ra cú pháp mới, bạn cần ít nhất phiên bản 15.7. Bạn có thể thấy rằng cũng được đề cập trong ghi chú phát hành của Microsoft, xem Ghi chú phát hành Visual Studio 2017 15.7 . Cảm ơn @MohamedElshawaf đã chỉ ra câu hỏi hợp lệ này.

Xin cũng lưu ý rằng trong trường hợp của tôi, ReSharper 2018.1 khi viết EDIT này chưa hỗ trợ C # 7.3. Khi ReSharper kích hoạt, nó làm nổi bật ràng buộc Enum là một lỗi cho tôi biết Không thể sử dụng 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' làm ràng buộc tham số loại . ReSharper đề xuất như là một sửa chữa nhanh chóng để loại bỏ ràng buộc 'Enum' của kiểu tham số T của phương thức

Tuy nhiên, nếu bạn tạm thời tắt ReSharper trong Công cụ -> Tùy chọn -> ReSharper Ultimate -> Chung bạn sẽ thấy rằng cú pháp hoàn toàn tốt nếu bạn sử dụng VS 15.7 trở lên và C # 7.3 trở lên.


1
Phiên bản VS nào bạn đang sử dụng?
mshwf

1
@MohamedElshawaf Tôi tin rằng đó là phiên bản 15.7 có hỗ trợ cho C # 7.3
Patrick Roberts

1
Tôi nghĩ rằng tốt hơn là viết where T : struct, Enum, để tránh truyền System.Enumchính nó như là tham số loại.
Mariusz Pawelski

Giống như @MariuszPawelski tôi viết struct, Enum. Lý do của tôi được giải thích trong câu trả lời và ý kiến ở đây .
Stephen Kennedy

Thông tin ReSharper thực sự giúp tôi. Lưu ý phiên bản xem trước mới nhất hỗ trợ tính năng này.
DalSoft

11

Tôi đã sửa đổi mẫu bằng dimarzionist. Phiên bản này sẽ chỉ hoạt động với Enums và không cho phép cấu trúc đi qua.

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

13
Tôi sẽ không trả về giá trị mặc định khi thất bại; Tôi sẽ để ngoại lệ lan truyền (giống như với Enum.Pude). Thay vào đó, sử dụng TryPude trả về một bool và trả về kết quả bằng cách sử dụng param.
Mark Simpson

1
OP muốn nó không phân biệt chữ hoa chữ thường, không phải vậy.
Konrad Morawski

9

Tôi đã cố gắng cải thiện mã một chút:

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

1
Điều này tốt hơn câu trả lời được chấp nhận vì nó cho phép bạn gọi defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)mặc dù bạn không biết đó là loại enum nào, chỉ có đối tượng là enum.
styfle

1
Tuy nhiên, việc kiểm tra trước IsDefinedsẽ phá hỏng trường hợp không nhạy cảm. Không giống như Parse, IsDefinedkhông có ignoreCaseđối số và MSDN cho biết nó chỉ khớp với trường hợp chính xác .
Nyerguds

5

Tôi có yêu cầu cụ thể khi tôi yêu cầu sử dụng enum với văn bản được liên kết với giá trị enum. Ví dụ: khi tôi sử dụng enum để chỉ định loại lỗi, nó được yêu cầu để mô tả chi tiết lỗi.

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

4

Hy vọng điều này hữu ích:

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

1
Nếu bạn cần sự vô cảm trong trường hợp, chỉ cần thay thế return (TValue)Enum.Parse(typeof (TValue), value);bằngreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos

3

Thật thú vị, rõ ràng điều này là có thể trong các langau khác (Được quản lý trực tiếp C ++, IL).

Để trích:

... Cả hai ràng buộc thực sự tạo ra IL hợp lệ và cũng có thể được sử dụng bởi C # nếu được viết bằng ngôn ngữ khác (bạn có thể khai báo các ràng buộc đó trong C ++ được quản lý hoặc trong IL).

Ai biết


2
Các tiện ích mở rộng được quản lý cho C ++ không có bất kỳ hỗ trợ nào cho thuốc generic, tôi nghĩ bạn có nghĩa là C ++ / CLI.
Ben Voigt

3

Đây là mất của tôi tại nó. Kết hợp từ câu trả lời và 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));
    }
}

Nguồn MSDN


2
Điều này không thực sự có ý nghĩa. Nếu TEnumthực sự là một loại Enum nhưng textlà một chuỗi rỗng thì bạn sẽ nhận được ArgumentExceptioncâu "TEnum phải là một loại Enum" mặc dù nó là.
Nick

3

Các câu trả lời hiện có là đúng với C # <= 7.2. Tuy nhiên, có một yêu cầu tính năng ngôn ngữ C # (gắn với yêu cầu tính năng corefx ) để cho phép các yêu cầu sau;

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

Tại thời điểm viết bài, tính năng này là "Đang thảo luận" tại các Cuộc họp Phát triển Ngôn ngữ.

BIÊN TẬP

Theo thông tin của nawfal , điều này đang được giới thiệu trong C # 7.3 .


1
Thảo luận thú vị ở đó, cảm ơn. Chưa có gì được thiết lập mặc dù (như vậy)
johnc

1
@johnc, rất đúng nhưng đáng ghi chú và đó một tính năng thường được hỏi. Tỷ lệ cược công bằng khi nó đến.
DiskJunky 27/03/18

1
Điều này sẽ đến trong C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/ . :)
nawfal

1

Tôi luôn thích điều này (bạn có thể sửa đổi khi thích hợp):

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

1

Tôi yêu thích giải pháp của Christopher Currens khi sử dụng IL nhưng đối với những người không muốn đối phó với việc kinh doanh phức tạp bao gồm MSIL vào quy trình xây dựng của họ, tôi đã viết chức năng tương tự trong C #.

Xin lưu ý rằng bạn không thể sử dụng hạn chế chung như where T : Enumvì Enum là loại đặc biệt. Vì vậy, tôi phải kiểm tra xem loại chung chung có thực sự là enum không.

Chức năng của tôi là:

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

1

Tôi đã gói giải pháp của Vivek vào một lớp tiện ích mà bạn có thể sử dụng lại. Xin lưu ý rằng bạn vẫn nên xác định các ràng buộc loại "trong đó T: struct, IConvertible" trên loại của bạn.

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

1

Tôi đã tạo một phần mở rộng Phương thức to get integer value from enum xem xét việc thực hiện phương thức

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

đây là cách sử dụng

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way

Trong khi nó có thể hoạt động, nó gần như không liên quan đến câu hỏi.
quetzalcoatl

1

Như đã nêu trong các câu trả lời khác trước đây; trong khi điều này không thể được thể hiện bằng mã nguồn, nó thực sự có thể được thực hiện ở cấp độ IL. Câu trả lời @Christopher Currens cho thấy IL làm điều đó như thế nào.

Với ExtraConstraint bổ trợ của Fody. Có một cách rất đơn giản, hoàn chỉnh với công cụ xây dựng, để đạt được điều này. Chỉ cần thêm các gói nuget của họ ( Fody, ExtraConstraints.Fody) vào dự án của bạn và thêm các ràng buộc như sau (Trích từ Readme của ExtraConstraint):

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

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

và Fody sẽ thêm IL cần thiết cho sự hạn chế hiện diện. Cũng lưu ý tính năng bổ sung của các đại biểu ràng buộc:

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

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

Về Enums, bạn cũng có thể muốn lưu ý về Enums.NET rất thú vị .


1

Đây là thực hiện của tôi. Về cơ bản, bạn có thể thiết lập bất kỳ thuộc tính nào và nó hoạt động.

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

0

Nếu bạn có thể sử dụng truyền trực tiếp sau đó, tôi đoán bạn có thể sử dụng System.Enumlớp cơ sở trong phương thức của mình, bất cứ khi nào cần thiết. Bạn chỉ cần thay thế các tham số loại cẩn thận. Vì vậy, việc thực hiện phương pháp sẽ như sau:

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

Sau đó, bạn có thể sử dụng nó như:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);

sử dụng Enum.ToObject()sẽ mang lại một kết quả linh hoạt hơn. Thêm vào đó, bạn có thể thực hiện các so sánh chuỗi mà không có độ nhạy trường hợp sẽ phủ nhận nhu cầu gọiToLower()
DiskJunky

-6

Để hoàn thiện, sau đây là một giải pháp Java. Tôi chắc chắn điều tương tự cũng có thể được thực hiện trong C #. Nó tránh phải chỉ định loại bất cứ nơi nào trong mã - thay vào đó, bạn chỉ định nó trong chuỗi bạn đang cố phân tích.

Vấn đề là không có cách nào để biết chuỗi liệt kê nào có thể khớp - vì vậy câu trả lời là giải quyết vấn đề đó.

Thay vì chỉ chấp nhận giá trị chuỗi, hãy chấp nhận Chuỗi có cả kiểu liệt kê và giá trị ở dạng "enumutions.value". Mã làm việc ở bên dưới - yêu cầu Java 1.8 trở lên. Điều này cũng sẽ làm cho XML chính xác hơn vì trong bạn sẽ thấy một cái gì đó như color = "Color.red" thay vì chỉ color = "red".

Bạn sẽ gọi phương thức acceptEnumeratedValue () bằng một chuỗi chứa tên giá trị dấu chấm tên enum.

Phương thức trả về giá trị liệt kê chính thức.

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


}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.