Chuyển đổi một chuỗi thành một enum trong C #


894

Cách tốt nhất để chuyển đổi một chuỗi thành giá trị liệt kê trong C # là gì?

Tôi có thẻ chọn HTML chứa các giá trị liệt kê. Khi trang được đăng, tôi muốn chọn giá trị (sẽ ở dạng chuỗi) và chuyển đổi nó thành giá trị liệt kê.

Trong một thế giới lý tưởng, tôi có thể làm một cái gì đó như thế này:

StatusEnum MyStatus = StatusEnum.Parse("Active");

nhưng đó không phải là một mã hợp lệ.

Câu trả lời:


1510

Trong .NET Core và .NET> 4 có một phương thức phân tích chung :

Enum.TryParse("Active", out StatusEnum myStatus);

Điều này cũng bao gồm outcác biến nội tuyến mới của C # 7 , do đó, điều này thực hiện phân tích thử, chuyển đổi sang loại enum rõ ràng và khởi tạo + điền vàomyStatus biến.

Nếu bạn có quyền truy cập vào C # 7 và .NET mới nhất thì đây là cách tốt nhất.

Câu trả lời gốc

Trong .NET, nó khá xấu (cho đến 4 hoặc cao hơn):

StatusEnum MyStatus = (StatusEnum) Enum.Parse(typeof(StatusEnum), "Active", true);

Tôi có xu hướng đơn giản hóa điều này với:

public static T ParseEnum<T>(string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

Sau đó tôi có thể làm:

StatusEnum MyStatus = EnumUtil.ParseEnum<StatusEnum>("Active");

Một tùy chọn được đề xuất trong các nhận xét là thêm tiện ích mở rộng, đủ đơn giản:

public static T ToEnum<T>(this string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

StatusEnum MyStatus = "Active".ToEnum<StatusEnum>();

Cuối cùng, bạn có thể muốn có một enum mặc định để sử dụng nếu chuỗi không thể được phân tích cú pháp:

public static T ToEnum<T>(this string value, T defaultValue) 
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    T result;
    return Enum.TryParse<T>(value, true, out result) ? result : defaultValue;
}

Điều này làm cho cuộc gọi này:

StatusEnum MyStatus = "Active".ToEnum(StatusEnum.None);

Tuy nhiên, tôi sẽ cẩn thận thêm một phương thức mở rộng như thế này vào string(không có kiểm soát không gian tên) nó sẽ xuất hiện trên tất cả các trường hợp stringcho dù chúng có giữ enum hay không (vì vậy 1234.ToString().ToEnum(StatusEnum.None)sẽ hợp lệ nhưng vô nghĩa). Tốt nhất là tránh làm lộn xộn các lớp lõi của Microsoft bằng các phương thức bổ sung chỉ áp dụng trong các ngữ cảnh rất cụ thể trừ khi toàn bộ nhóm phát triển của bạn hiểu rất rõ về những tiện ích mở rộng đó làm gì.


17
Nếu hiệu suất là quan trọng (luôn luôn là) câu trả lời chk được đưa ra bởi Mckenzieg1 bên dưới: stackoverflow.com/questions/16100/ chủ
Nash

28
@avinashr nói đúng về câu trả lời của @ McKenzieG1, nhưng nó không quan trọng. Ví dụ, nó sẽ là một tối ưu hóa vô nghĩa để lo lắng về phân tích enum nếu bạn đang thực hiện một cuộc gọi DB cho mỗi phân tích cú pháp.
Keith

4
@HM Tôi không nghĩ tiện ích mở rộng phù hợp ở đây - đó là một trường hợp đặc biệt và tiện ích mở rộng sẽ áp dụng cho mọi chuỗi. Nếu bạn thực sự muốn làm điều đó mặc dù nó sẽ là một thay đổi nhỏ.
Keith

7
Enum.TryPude thì sao?
Elaine

15
rất đẹp. bạn cần một T: struct trong ví dụ cuối cùng của bạn.
bbrik

331

Sử dụng Enum.TryParse<T>(String, T)(≥ .NET 4.0):

StatusEnum myStatus;
Enum.TryParse("Active", out myStatus);

Nó có thể được đơn giản hóa hơn nữa với kiểu tham số của C # 7.0 :

Enum.TryParse("Active", out StatusEnum myStatus);

45
Thêm tham số boolean giữa cho phân biệt chữ hoa chữ thường và đây là giải pháp an toàn và thanh lịch nhất cho đến nay.
DanM7

18
Thôi nào, có bao nhiêu bạn đã thực hiện câu trả lời được chọn từ năm 2008 để chỉ cuộn xuống và thấy đây là câu trả lời tốt hơn (hiện đại).
TEK

@TEK Tôi thực sự thích câu trả lời năm 2008.
Zero3

Tôi không hiểu Parseđưa ra các ngoại lệ giải thích cho sự cố xảy ra với chuyển đổi (giá trị là null, trống hoặc không có hằng số enum tương ứng), tốt hơn so với TryParsegiá trị trả về boolean (loại bỏ lỗi cụ thể)
yair

2
Enum.TryPude <T> (String, T) bị thiếu sót khi phân tích chuỗi số nguyên. Ví dụ: mã này sẽ phân tích thành công một chuỗi vô nghĩa như một enum vô nghĩa: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net

196

Lưu ý rằng hiệu suất của Enum.Parse()là khủng khiếp, bởi vì nó được thực hiện thông qua sự phản ánh. (Điều tương tự cũng đúng Enum.ToString, đi theo cách khác.)

Nếu bạn cần chuyển đổi chuỗi thành Enums trong mã nhạy cảm với hiệu suất, cách tốt nhất của bạn là tạo một chuỗi Dictionary<String,YourEnum>khi khởi động và sử dụng chuỗi đó để thực hiện chuyển đổi.


10
Tôi đã đo 3ms để chuyển đổi một chuỗi thành Enum trong lần chạy đầu tiên, trên máy tính để bàn. (Chỉ để minh họa mức độ tuyệt vời).
Matthieu Charbonnier

12
Wow 3ms là đơn đặt hàng có cường độ khủng khiếp
John Stock

1
bạn có thể thêm một mẫu mã xung quanh điều này không, vì vậy chúng tôi có ý tưởng về cách thay thế và sử dụng
biến áp

Nếu ứng dụng của bạn được sử dụng bởi 1 triệu người => nó sẽ tăng thêm 50 giờ trong cuộc sống của con người bạn đang sử dụng :) Trên một trang sử dụng. : P
Cătălin Rădoi


31

Bạn có thể sử dụng các phương thức mở rộng ngay bây giờ:

public static T ToEnum<T>(this string value, bool ignoreCase = true)
{
    return (T) Enum.Parse(typeof (T), value, ignoreCase);
}

Và bạn có thể gọi chúng theo mã dưới đây (ở đây, FilterTypelà một loại enum):

FilterType filterType = type.ToEnum<FilterType>();

1
Tôi đã cập nhật điều này để lấy giá trị làm đối tượng và chuyển nó thành chuỗi bên trong phương thức này. Bằng cách này, tôi có thể lấy một giá trị int .ToEnum thay vì chỉ các chuỗi.
RealSollyM

2
@SollyM Tôi muốn nói rằng đó là một ý tưởng khủng khiếp, sau đó phương thức mở rộng này sẽ áp dụng cho tất cả các loại đối tượng. Hai phương thức mở rộng, một cho chuỗi và một cho int, theo tôi sẽ sạch hơn và an toàn hơn nhiều.
Svish

@Svish, đó là sự thật. Lý do duy nhất tôi làm điều này là vì mã của chúng tôi chỉ được sử dụng nội bộ và tôi muốn tránh viết 2 phần mở rộng. Và vì lần duy nhất chúng tôi chuyển đổi sang Enum là bằng chuỗi hoặc int, tôi đã không thấy nó là một vấn đề khác.
RealSollyM

3
@SollyM Nội bộ hay không, tôi vẫn là người duy trì và sử dụng mã của mình: PI sẽ khó chịu nếu tôi nhận được ToEnum trong mỗi menu intellisense, và như bạn nói, vì lần duy nhất bạn chuyển đổi thành enum là từ chuỗi hoặc int, bạn có thể khá chắc chắn rằng bạn sẽ chỉ cần hai phương thức đó. Và hai phương thức không nhiều hơn một, đặc biệt là khi chúng nhỏ và thuộc loại tiện ích: P
Svish

20
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

Vì vậy, nếu bạn có một tâm trạng tên enum, nó sẽ trông như thế này:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

   // ...
   Mood m = (Mood) Enum.Parse(typeof(Mood), "Happy", true);
   Console.WriteLine("My mood is: {0}", m.ToString());

18

THƯỞNG

enum Example
{
    One = 1,
    Two = 2,
    Three = 3
}

Enum.(Try)Parse() chấp nhận nhiều đối số, được phân tách bằng dấu phẩy và kết hợp chúng với nhị phân 'hoặc'| . Bạn không thể vô hiệu hóa điều này và theo tôi bạn gần như không bao giờ muốn nó.

var x = Enum.Parse("One,Two"); // x is now Three

Ngay cả khi Threekhông được xác định, xvẫn sẽ nhận được giá trị int 3. Điều đó thậm chí còn tệ hơn: Enum.Pude () có thể cung cấp cho bạn một giá trị thậm chí không được xác định cho enum!

Tôi sẽ không muốn trải nghiệm hậu quả của người dùng, sẵn sàng hay không sẵn lòng, kích hoạt hành vi này.

Ngoài ra, như được đề cập bởi những người khác, hiệu suất thấp hơn lý tưởng cho các enum lớn, cụ thể là tuyến tính về số lượng giá trị có thể.

Tôi đề nghị như sau:

    public static bool TryParse<T>(string value, out T result)
        where T : struct
    {
        var cacheKey = "Enum_" + typeof(T).FullName;

        // [Use MemoryCache to retrieve or create&store a dictionary for this enum, permanently or temporarily.
        // [Implementation off-topic.]
        var enumDictionary = CacheHelper.GetCacheItem(cacheKey, CreateEnumDictionary<T>, EnumCacheExpiration);

        return enumDictionary.TryGetValue(value.Trim(), out result);
    }

    private static Dictionary<string, T> CreateEnumDictionary<T>()
    {
        return Enum.GetValues(typeof(T))
            .Cast<T>()
            .ToDictionary(value => value.ToString(), value => value, StringComparer.OrdinalIgnoreCase);
    }

4
Trong thực tế điều này là rất hữu ích để biết rằng Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Có nghĩa là bạn có thể thiết lập các giá trị enum của mình dưới dạng lũy ​​thừa 2 và bạn có một cách rất dễ dàng để phân tích nhiều cờ boolean, ví dụ. "Sử dụngSSL, NoRetries, Đồng bộ hóa". Trong thực tế đó có lẽ là những gì nó được thiết kế cho.
pcdev

16

Enum.Pude là bạn của bạn:

StatusEnum MyStatus = (StatusEnum)Enum.Parse(typeof(StatusEnum), "Active");

13

Bạn có thể mở rộng câu trả lời được chấp nhận với giá trị mặc định để tránh trường hợp ngoại lệ:

public static T ParseEnum<T>(string value, T defaultValue) where T : struct
{
    try
    {
        T enumValue;
        if (!Enum.TryParse(value, true, out enumValue))
        {
            return defaultValue;
        }
        return enumValue;
    }
    catch (Exception)
    {
        return defaultValue;
    }
}

Sau đó, bạn gọi nó như:

StatusEnum MyStatus = EnumUtil.ParseEnum("Active", StatusEnum.None);

Nếu giá trị mặc định không phải là enum, Enum.TryPude sẽ thất bại và đưa ra một ngoại lệ được bắt.

Sau nhiều năm sử dụng chức năng này trong mã của chúng tôi ở nhiều nơi, có lẽ thật tốt khi thêm thông tin mà thao tác này có chi phí hiệu suất!


Tôi không thích các giá trị mặc định. Nó có thể dẫn đến kết quả không thể đoán trước.
Daniël Tulp

5
Khi nào thì điều này sẽ bao giờ ném một ngoại lệ?
andleer

@andleer nếu giá trị enum không khớp với cùng loại enum với giá trị mặc định
Nelly

@Nelly Mã cũ ở đây nhưng defaultValuekiểu trả về và phương thức đều thuộc loại T. Nếu các loại khác nhau, bạn sẽ nhận được lỗi thời gian biên dịch: "không thể chuyển đổi từ 'ConsoleApp1.Size' thành 'ConsoleApp1.Color'" hoặc bất kỳ loại nào của bạn.
andleer

@andleer, tôi xin lỗi câu trả lời cuối cùng của tôi cho bạn là không chính xác. Có thể phương thức này ném Syste.ArgumentException trong trường hợp ai đó gọi hàm này với giá trị mặc định không phải là kiểu enum. Với c # 7.0, tôi không thể tạo mệnh đề where của T: Enum. Đó là lý do tại sao tôi bắt được sở hữu này với một thử bắt.
Nelly

8

Chúng tôi không thể cho rằng đầu vào hoàn toàn hợp lệ và đi kèm với biến thể câu trả lời của @ Keith:

public static TEnum ParseEnum<TEnum>(string value) where TEnum : struct
{
    TEnum tmp; 
    if (!Enum.TryParse<TEnum>(value, true, out tmp))
    {
        tmp = new TEnum();
    }
    return tmp;
}

7
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str) 
{ 
    return (T) Enum.Parse(typeof(T), str);
}

5

Phân tích chuỗi thành TEnum mà không cần phương thức try / Catch và không có phương thức TryPude () từ .NET 4.5

/// <summary>
/// Parses string to TEnum without try/catch and .NET 4.5 TryParse()
/// </summary>
public static bool TryParseToEnum<TEnum>(string probablyEnumAsString_, out TEnum enumValue_) where TEnum : struct
{
    enumValue_ = (TEnum)Enum.GetValues(typeof(TEnum)).GetValue(0);
    if(!Enum.IsDefined(typeof(TEnum), probablyEnumAsString_))
        return false;

    enumValue_ = (TEnum) Enum.Parse(typeof(TEnum), probablyEnumAsString_);
    return true;
}

1
Liệu có cần thiết phải tạo một mô tả nếu mã đã chứa một mô tả? Ok, tôi đã làm điều này :)
jite.gs

3

Mã siêu đơn giản sử dụng TryPude:

var value = "Active";

StatusEnum status;
if (!Enum.TryParse<StatusEnum>(value, out status))
    status = StatusEnum.Unknown;

2

Tôi thích giải pháp phương pháp mở rộng ..

namespace System
{
    public static class StringExtensions
    {

        public static bool TryParseAsEnum<T>(this string value, out T output) where T : struct
        {
            T result;

            var isEnum = Enum.TryParse(value, out result);

            output = isEnum ? result : default(T);

            return isEnum;
        }
    }
}

Dưới đây thực hiện của tôi với các bài kiểm tra.

using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
using static System.Console;

private enum Countries
    {
        NorthAmerica,
        Europe,
        Rusia,
        Brasil,
        China,
        Asia,
        Australia
    }

   [TestMethod]
        public void StringExtensions_On_TryParseAsEnum()
        {
            var countryName = "Rusia";

            Countries country;
            var isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsTrue(isCountry);
            AreEqual(Countries.Rusia, country);

            countryName = "Don't exist";

            isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsFalse(isCountry);
            AreEqual(Countries.NorthAmerica, country); // the 1rst one in the enumeration
        }

1
public static T ParseEnum<T>(string value)            //function declaration  
{
    return (T) Enum.Parse(typeof(T), value);
}

Importance imp = EnumUtil.ParseEnum<Importance>("Active");   //function call

==================== Một chương trình hoàn chỉnh ====================

using System;

class Program
{
    enum PetType
    {
    None,
    Cat = 1,
    Dog = 2
    }

    static void Main()
    {

    // Possible user input:
    string value = "Dog";

    // Try to convert the string to an enum:
    PetType pet = (PetType)Enum.Parse(typeof(PetType), value);

    // See if the conversion succeeded:
    if (pet == PetType.Dog)
    {
        Console.WriteLine("Equals dog.");
    }
    }
}
-------------
Output

Equals dog.

1

Tôi đã sử dụng lớp (phiên bản gõ mạnh của Enum với phân tích cú pháp và cải tiến hiệu suất). Tôi đã tìm thấy nó trên GitHub và nó cũng hoạt động với .NET 3.5. Nó có một số bộ nhớ trên vì nó đệm một từ điển.

StatusEnum MyStatus = Enum<StatusEnum>.Parse("Active");

Blogpost là Enums - Cú pháp tốt hơn, hiệu suất được cải thiện và TryPude trong NET 3.5 .

Và mã: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.L Library / System /Eumum.cs


1

Đối với hiệu suất, điều này có thể giúp:

    private static Dictionary<Type, Dictionary<string, object>> dicEnum = new Dictionary<Type, Dictionary<string, object>>();
    public static T ToEnum<T>(this string value, T defaultValue)
    {
        var t = typeof(T);
        Dictionary<string, object> dic;
        if (!dicEnum.ContainsKey(t))
        {
            dic = new Dictionary<string, object>();
            dicEnum.Add(t, dic);
            foreach (var en in Enum.GetValues(t))
                dic.Add(en.ToString(), en);
        }
        else
            dic = dicEnum[t];
        if (!dic.ContainsKey(value))
            return defaultValue;
        else
            return (T)dic[value];
    }

1

Tôi thấy rằng ở đây trường hợp với các giá trị enum có giá trị EnumMember không được xem xét. Vì vậy, ở đây chúng tôi đi:

using System.Runtime.Serialization;

public static TEnum ToEnum<TEnum>(this string value, TEnum defaultValue) where TEnum : struct
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    TEnum result;
    var enumType = typeof(TEnum);
    foreach (var enumName in Enum.GetNames(enumType))
    {
        var fieldInfo = enumType.GetField(enumName);
        var enumMemberAttribute = ((EnumMemberAttribute[]) fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), true)).FirstOrDefault();
        if (enumMemberAttribute?.Value == value)
        {
            return Enum.TryParse(enumName, true, out result) ? result : defaultValue;
        }
    }

    return Enum.TryParse(value, true, out result) ? result : defaultValue;
}

Và ví dụ về enum đó:

public enum OracleInstanceStatus
{
    Unknown = -1,
    Started = 1,
    Mounted = 2,
    Open = 3,
    [EnumMember(Value = "OPEN MIGRATE")]
    OpenMigrate = 4
}

1

Bạn phải sử dụng Enum.Pude để lấy giá trị đối tượng từ Enum, sau đó bạn phải thay đổi giá trị đối tượng thành giá trị enum cụ thể. Việc truyền tới giá trị enum có thể được thực hiện bằng cách sử dụng Convert.ChangeType. Xin hãy xem đoạn mã sau

public T ConvertStringValueToEnum<T>(string valueToParse){
    return Convert.ChangeType(Enum.Parse(typeof(T), valueToParse, true), typeof(T));
}

1

Hãy thử mẫu này:

 public static T GetEnum<T>(string model)
    {
        var newModel = GetStringForEnum(model);

        if (!Enum.IsDefined(typeof(T), newModel))
        {
            return (T)Enum.Parse(typeof(T), "None", true);
        }

        return (T)Enum.Parse(typeof(T), newModel.Result, true);
    }

    private static Task<string> GetStringForEnum(string model)
    {
        return Task.Run(() =>
        {
            Regex rgx = new Regex("[^a-zA-Z0-9 -]");
            var nonAlphanumericData = rgx.Matches(model);
            if (nonAlphanumericData.Count < 1)
            {
                return model;
            }
            foreach (var item in nonAlphanumericData)
            {
                model = model.Replace((string)item, "");
            }
            return model;
        });
    }

Trong mẫu này, bạn có thể gửi mọi chuỗi và đặt Enum. Nếu bạn Enumcó dữ liệu mà bạn muốn, hãy trả lại dữ liệu đó theo Enumkiểu của bạn .


1
Bạn đang ghi đè lên newModelmỗi dòng, vì vậy nếu nó chứa dấu gạch ngang, nó sẽ không được thay thế. Ngoài ra, bạn không phải kiểm tra xem chuỗi có chứa gì không, Replacedù sao bạn cũng có thể gọi :var newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen

@LarsKristensen Vâng, chúng tôi có thể tạo ra một phương pháp loại bỏ ký tự không phải âm thanh.
AmirReza-Farahlagha

1

Không chắc chắn khi điều này được thêm vào nhưng trên lớp Enum bây giờ có một

Parse<TEnum>(stringValue)

Được sử dụng như vậy với ví dụ trong câu hỏi:

var MyStatus = Enum.Parse<StatusEnum >("Active")

hoặc bỏ qua vỏ bằng cách:

var MyStatus = Enum.Parse<StatusEnum >("active", true)

Đây là phương pháp dịch ngược mà nó sử dụng:

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value) where TEnum : struct
    {
      return Enum.Parse<TEnum>(value, false);
    }

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value, bool ignoreCase) where TEnum : struct
    {
      TEnum result;
      Enum.TryParse<TEnum>(value, ignoreCase, true, out result);
      return result;
    }

0
        <Extension()>
    Public Function ToEnum(Of TEnum)(ByVal value As String, ByVal defaultValue As TEnum) As TEnum
        If String.IsNullOrEmpty(value) Then
            Return defaultValue
        End If

        Return [Enum].Parse(GetType(TEnum), value, True)
    End Function

0
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
    return defaultValue;

return Enum.Parse(typeof(TEnum), value, true);}

0

Nếu tên thuộc tính khác với tên bạn muốn gọi (nghĩa là khác biệt ngôn ngữ), bạn có thể làm như thế này:

MyType.cs

using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[JsonConverter(typeof(StringEnumConverter))]
public enum MyType
{
    [EnumMember(Value = "person")]
    Person,
    [EnumMember(Value = "annan_deltagare")]
    OtherPerson,
    [EnumMember(Value = "regel")]
    Rule,
}

EnumExtensions.cs

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public static class EnumExtensions
{
    public static TEnum ToEnum<TEnum>(this string value) where TEnum : Enum
    {
        var jsonString = $"'{value.ToLower()}'";
        return JsonConvert.DeserializeObject<TEnum>(jsonString, new StringEnumConverter());
    }

    public static bool EqualsTo<TEnum>(this string strA, TEnum enumB) where TEnum : Enum
    {
        TEnum enumA;
        try
        {
            enumA = strA.ToEnum<TEnum>();
        }
        catch
        {
            return false;
        }
        return enumA.Equals(enumB);
    }
}

Chương trình.cs

public class Program
{
    static public void Main(String[] args) 
    { 
        var myString = "annan_deltagare";
        var myType = myString.ToEnum<MyType>();
        var isEqual = myString.EqualsTo(MyType.OtherPerson);
        //Output: true
    }     
}
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.