Làm thế nào để TryParse cho giá trị Enum?


94

Tôi muốn viết một hàm có thể xác thực một giá trị nhất định (được truyền dưới dạng chuỗi) so với các giá trị có thể có của một enum. Trong trường hợp khớp, nó sẽ trả về thể hiện enum; nếu không, nó sẽ trả về một giá trị mặc định.

Hàm không được sử dụng nội bộ try/ catch, loại trừ việc sử dụng Enum.Parse, sẽ ném ra một ngoại lệ khi đưa ra một đối số không hợp lệ.

Tôi muốn sử dụng một cái gì đó dọc theo dòng của một TryParsehàm để triển khai điều này:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}

8
Tôi không hiểu câu hỏi này; bạn đang nói "Tôi muốn giải quyết vấn đề này, nhưng tôi không muốn sử dụng bất kỳ phương pháp nào có thể cho tôi giải pháp." Vấn đề ở đây là gì?
Domenic

1
Bạn không thích thử / bắt giải pháp nào? Nếu bạn đang cố gắng tránh Ngoại lệ vì chúng 'tốn kém', vui lòng cho bản thân nghỉ ngơi. Trong 99% trường hợp, ngoại lệ Chi phí để ném / bắt là không đáng kể so với mã chính của bạn.
Giải

1
Chi phí xử lý ngoại lệ không quá tệ. Địa ngục, các triển khai bên trong của tất cả chuyển đổi liệt kê này có đầy đủ các xử lý ngoại lệ. Tôi thực sự không thích các ngoại lệ bị ném và bắt trong logic ứng dụng bình thường. Đôi khi có thể hữu ích khi phá vỡ tất cả các ngoại lệ đang được ném ra (ngay cả khi chúng bị bắt). Ném ngoại lệ ở khắp mọi nơi sẽ làm cho rằng rất nhiều phiền toái hơn để sử dụng :)
Thorarin

3
@Domenic: Tôi chỉ đang tìm kiếm một giải pháp tốt hơn những gì tôi đã biết. Có bạn nào lên chuyên mục đường sắt để hỏi đường đi hoặc tàu mà mình đã biết chưa :).
Manish Basantani

2
@Amby, chi phí chỉ cần nhập một khối thử / bắt là không đáng kể. Cái giá phải trả của việc NẠP một ngoại lệ không phải là ngoại lệ, nhưng sau đó nó được cho là đặc biệt, phải không? Ngoài ra, đừng nói "chúng tôi không bao giờ biết" ... hãy lập hồ sơ mã và tìm hiểu. Đừng lãng phí thời gian của bạn để tự hỏi nếu cái gì đó là chậm, hãy TÌM HIỂU!
akmad 24/09/09

Câu trả lời:


31

Như những người khác đã nói, bạn phải thực hiện của riêng bạn TryParse. Simon Mourier đang cung cấp một bản triển khai đầy đủ để đảm bảo mọi thứ.

Nếu bạn đang sử dụng enum bitfield (tức là cờ), bạn cũng phải xử lý một chuỗi giống như một chuỗi "MyEnum.Val1|MyEnum.Val2"kết hợp của hai giá trị enum. Nếu bạn chỉ gọi Enum.IsDefinedbằng chuỗi này, nó sẽ trả về false, mặc dù Enum.Parsexử lý nó một cách chính xác.

Cập nhật

Như đã được đề cập bởi Lisa và Christian trong các nhận xét, Enum.TryParsehiện đã có sẵn cho C # trong .NET4 trở lên.

Tài liệu MSDN


Có lẽ ít sexy nhất, nhưng tôi đồng ý rằng điều này chắc chắn là tốt nhất cho đến khi mã của bạn được chuyển sang .NET 4.
Lisa

1
Như đã đề cập bên dưới, nhưng không thực sự hiển thị: Kể từ .Net 4 Enum.TryParse có sẵn và hoạt động mà không cần mã hóa thêm. Thông tin khác có sẵn từ MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

106

Enum.IsDefined sẽ hoàn thành công việc. Nó có thể không hiệu quả như TryParse, nhưng nó sẽ hoạt động mà không cần xử lý ngoại lệ.

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
    if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
        return defaultValue;

    return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
}

Đáng chú ý: một TryParsephương pháp đã được thêm vào .NET 4.0.


1
Câu trả lời tốt nhất mà tôi đã thấy cho đến nay ... không try / catch, không GetNames :)
Thomas Levesque


6
cũng có không bỏ qua trường hợp trên IsDefined
Anthony Johnston

2
@Anthony: nếu bạn muốn hỗ trợ phân biệt chữ hoa chữ thường, bạn sẽ cần GetNames. Trong nội bộ, tất cả các phương pháp này (bao gồm Parse) sử dụng GetHashEntry, phản ánh thực tế - một lần. Trên mặt tươi sáng, .NET 4.0 có một TryParse, và nó chung chung quá :)
Thorarin

+1 Nó đã lưu ngày của tôi! Tôi đang sao lưu một loạt mã từ .NET 4 sang .NET 3.5 và bạn đã cứu tôi :)
daitangio

20

Đây là một triển khai tùy chỉnh của EnumTryParse. Không giống như các triển khai thông thường khác, nó cũng hỗ trợ enum được đánh dấu bằng Flagsthuộc tính.

    /// <summary>
    /// Converts the string representation of an enum to its Enum equivalent value. A return value indicates whether the operation succeeded.
    /// This method does not rely on Enum.Parse and therefore will never raise any first or second chance exception.
    /// </summary>
    /// <param name="type">The enum target type. May not be null.</param>
    /// <param name="input">The input text. May be null.</param>
    /// <param name="value">When this method returns, contains Enum equivalent value to the enum contained in input, if the conversion succeeded.</param>
    /// <returns>
    /// true if s was converted successfully; otherwise, false.
    /// </returns>
    public static bool EnumTryParse(Type type, string input, out object value)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        if (!type.IsEnum)
            throw new ArgumentException(null, "type");

        if (input == null)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        input = input.Trim();
        if (input.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        string[] names = Enum.GetNames(type);
        if (names.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        Type underlyingType = Enum.GetUnderlyingType(type);
        Array values = Enum.GetValues(type);
        // some enums like System.CodeDom.MemberAttributes *are* flags but are not declared with Flags...
        if ((!type.IsDefined(typeof(FlagsAttribute), true)) && (input.IndexOfAny(_enumSeperators) < 0))
            return EnumToObject(type, underlyingType, names, values, input, out value);

        // multi value enum
        string[] tokens = input.Split(_enumSeperators, StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        ulong ul = 0;
        foreach (string tok in tokens)
        {
            string token = tok.Trim(); // NOTE: we don't consider empty tokens as errors
            if (token.Length == 0)
                continue;

            object tokenValue;
            if (!EnumToObject(type, underlyingType, names, values, token, out tokenValue))
            {
                value = Activator.CreateInstance(type);
                return false;
            }

            ulong tokenUl;
            switch (Convert.GetTypeCode(tokenValue))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                    tokenUl = (ulong)Convert.ToInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;

                //case TypeCode.Byte:
                //case TypeCode.UInt16:
                //case TypeCode.UInt32:
                //case TypeCode.UInt64:
                default:
                    tokenUl = Convert.ToUInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;
            }

            ul |= tokenUl;
        }
        value = Enum.ToObject(type, ul);
        return true;
    }

    private static char[] _enumSeperators = new char[] { ',', ';', '+', '|', ' ' };

    private static object EnumToObject(Type underlyingType, string input)
    {
        if (underlyingType == typeof(int))
        {
            int s;
            if (int.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(uint))
        {
            uint s;
            if (uint.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ulong))
        {
            ulong s;
            if (ulong.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(long))
        {
            long s;
            if (long.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(short))
        {
            short s;
            if (short.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ushort))
        {
            ushort s;
            if (ushort.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(byte))
        {
            byte s;
            if (byte.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(sbyte))
        {
            sbyte s;
            if (sbyte.TryParse(input, out s))
                return s;
        }

        return null;
    }

    private static bool EnumToObject(Type type, Type underlyingType, string[] names, Array values, string input, out object value)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (string.Compare(names[i], input, StringComparison.OrdinalIgnoreCase) == 0)
            {
                value = values.GetValue(i);
                return true;
            }
        }

        if ((char.IsDigit(input[0]) || (input[0] == '-')) || (input[0] == '+'))
        {
            object obj = EnumToObject(underlyingType, input);
            if (obj == null)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
            value = obj;
            return true;
        }

        value = Activator.CreateInstance(type);
        return false;
    }

1
bạn đã cung cấp cách triển khai tốt nhất và tôi đã sử dụng nó cho mục đích của riêng mình; tuy nhiên, tôi tự hỏi tại sao bạn lại sử dụng Activator.CreateInstance(type)để tạo giá trị enum mặc định mà không phải Enum.ToObject(type, 0). Chỉ là một vấn đề của hương vị?
Pierre Arnaud

1
@Pierre - Hmmm ... không, nó có vẻ tự nhiên hơn vào thời điểm đó :-) Có lẽ Enum.ToObject nhanh hơn vì nó sử dụng nội bộ cuộc gọi nội bộ InternalBoxEnum? Tôi không bao giờ kiểm tra rằng ...
Simon Mourier

2
Như đã đề cập bên dưới, nhưng không thực sự hiển thị: Kể từ .Net 4 Enum.TryParse có sẵn và hoạt động mà không cần mã hóa thêm. Thông tin khác có sẵn từ MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

16

Cuối cùng, bạn phải thực hiện điều này xung quanh Enum.GetNames:

public bool TryParseEnum<T>(string str, bool caseSensitive, out T value) where T : struct {
    // Can't make this a type constraint...
    if (!typeof(T).IsEnum) {
        throw new ArgumentException("Type parameter must be an enum");
    }
    var names = Enum.GetNames(typeof(T));
    value = (Enum.GetValues(typeof(T)) as T[])[0];  // For want of a better default
    foreach (var name in names) {
        if (String.Equals(name, str, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) {
            value = (T)Enum.Parse(typeof(T), name);
            return true;
        }
    }
    return false;
}

Ghi chú bổ sung:

  • Enum.TryParseđược bao gồm trong .NET 4. Xem tại đây http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Một cách tiếp cận khác là bọc trực tiếp Enum.Parsebắt ngoại lệ được ném ra khi nó không thành công. Điều này có thể nhanh hơn khi tìm thấy kết quả phù hợp, nhưng sẽ có thể chậm hơn nếu không. Tùy thuộc vào dữ liệu bạn đang xử lý, điều này có thể là một cải tiến thực tế hoặc không.

CHỈNH SỬA: Vừa thấy một triển khai tốt hơn về điều này, lưu trữ thông tin cần thiết: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5


Tôi sẽ đề xuất sử dụng default (T) để đặt giá trị mặc định. Hóa ra điều này sẽ không hoạt động cho tất cả các enum. Ví dụ: Nếu kiểu cơ bản cho enum là int default (T) sẽ luôn trả về 0, có thể có hoặc không hợp lệ cho enum.
Daniel Ballinger

Việc triển khai tại blog của Damieng không hỗ trợ enums với Flagsthuộc tính.
Uwe Keim

9

Dựa trên .NET 4.5

Mã mẫu bên dưới

using System;

enum Importance
{
    None,
    Low,
    Medium,
    Critical
}

class Program
{
    static void Main()
    {
    // The input value.
    string value = "Medium";

    // An unitialized variable.
    Importance importance;

    // Call Enum.TryParse method.
    if (Enum.TryParse(value, out importance))
    {
        // We now have an enum type.
        Console.WriteLine(importance == Importance.Medium);
    }
    }
}

Tham khảo: http://www.dotnetperls.com/enum-parse


4

Tôi có một triển khai được tối ưu hóa mà bạn có thể sử dụng trong UnconstrainedMelody . Về mặt hiệu quả, nó chỉ là bộ nhớ đệm danh sách tên, nhưng nó đang làm như vậy theo một cách tốt đẹp, được đánh máy mạnh mẽ, hạn chế chung chung :)


4
enum EnumStatus
{
    NAO_INFORMADO = 0,
    ENCONTRADO = 1,
    BLOQUEADA_PELO_ENTREGADOR = 2,
    DISPOSITIVO_DESABILITADO = 3,
    ERRO_INTERNO = 4,
    AGARDANDO = 5
}

...

if (Enum.TryParse<EnumStatus>(item.status, out status)) {

}

2

Hiện tại không có Enum.TryParse. Điều này đã được yêu cầu trên Connect ( Vẫn không có Enum.TryParse ) và nhận được phản hồi cho biết có thể được đưa vào khuôn khổ tiếp theo sau .NET 3.5. Bạn sẽ phải thực hiện các giải pháp thay thế được đề xuất ngay bây giờ.


1

Cách duy nhất để tránh xử lý ngoại lệ là sử dụng phương thức GetNames () và tất cả chúng ta đều biết rằng không nên lạm dụng ngoại lệ đối với logic ứng dụng thông thường :)


1
Đó không phải là cách duy nhất. Enum.IsDefined (..) sẽ ngăn chặn các ngoại lệ được ném vào mã người dùng.
Thorarin

1

Bộ nhớ đệm là một hàm / từ điển được tạo động có được phép không?

Bởi vì bạn không (dường như) biết trước loại enum, lần thực thi đầu tiên có thể tạo ra thứ gì đó mà các lần thực thi tiếp theo có thể tận dụng.

Bạn thậm chí có thể lưu trữ kết quả của Enum.GetNames ()

Bạn đang cố gắng tối ưu hóa cho CPU hoặc Bộ nhớ? Bạn có thực sự cần?


Ý tưởng là tối ưu hóa CPU. Đồng ý rằng tôi có thể làm điều đó với chi phí bộ nhớ. Nhưng nó không phải là giải pháp tôi đang tìm kiếm. Cảm ơn.
Manish Basantani

0

Như những người khác đã nói, nếu bạn không sử dụng Try & Catch, bạn cần sử dụng IsDefined hoặc GetNames ... Dưới đây là một số mẫu ... về cơ bản chúng đều giống nhau, mẫu đầu tiên xử lý các enums nullable. Tôi thích cái thứ 2 hơn vì nó là một phần mở rộng trên chuỗi, không phải enums ... nhưng bạn có thể kết hợp chúng theo ý muốn!

  • www.objectreference.net/post/Enum-TryParse-Extension-Method.aspx
  • flatlinerdoa.spaces.live.com/blog/cns!17124D03A9A052B0!605.entry
  • mironabramson.com/blog/post/2008/03/Aosystem-version-for-the-missing-method-EnumTryParse.aspx
  • lazyloading.blogspot.com/2008/04/enumtryparse-with-net-35-extension.html

0

Không có TryParse vì loại Enum không được biết cho đến thời gian chạy. Một TryParse tuân theo phương pháp tương tự như phương thức Date.TryParse sẽ tạo ra một lỗi chuyển đổi ngầm định trên tham số ByRef.

Tôi đề nghị làm điều gì đó như thế này:

//1 line call to get value
MyEnums enumValue = (Sections)EnumValue(typeof(Sections), myEnumTextValue, MyEnums.SomeEnumDefault);

//Put this somewhere where you can reuse
public static object EnumValue(System.Type enumType, string value, object NotDefinedReplacement)
{
    if (Enum.IsDefined(enumType, value)) {
        return Enum.Parse(enumType, value);
    } else {
        return Enum.Parse(enumType, NotDefinedReplacement);
    }
}

Đối với Trycác phương thức có kết quả có thể là kiểu giá trị hoặc ở đâu nullcó thể là kết quả hợp lệ (ví dụ: Dictionary.TryGetValue, which has both such traits), the normal pattern is for a phương thức Try` để trả về boolvà chuyển kết quả dưới dạng outtham số. Đối với những phương thức trả về kiểu lớp nullkhông phải là kết quả hợp lệ, không có khó khăn gì khi sử dụng nulltrả về để chỉ ra sự thất bại.
supercat

-1

Hãy xem chính lớp Enum (struct?). Có một phương pháp Phân tích cú pháp nhưng tôi không chắc về phương pháp phân tích cú pháp.


Tôi biết về phương thức Enum.Parse (typeof (TEnum), strEnumValue). Nó ném ArgumentException nếu strEnumValue không hợp lệ. Looking for TryParse ........
Manish Basantani

-2

Phương thức này sẽ chuyển đổi một loại enum:

  public static TEnum ToEnum<TEnum>(object EnumValue, TEnum defaultValue)
    {
        if (!Enum.IsDefined(typeof(TEnum), EnumValue))
        {
            Type enumType = Enum.GetUnderlyingType(typeof(TEnum));
            if ( EnumValue.GetType() == enumType )
            {
                string name = Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue);
                if( name != null)
                    return (TEnum)Enum.Parse(typeof(TEnum), name);
                return defaultValue;
            }
        }
        return (TEnum)Enum.Parse(typeof(TEnum), EnumValue.ToString());
    } 

Nó kiểm tra loại cơ bản và lấy tên dựa trên nó để phân tích cú pháp. Nếu mọi thứ không thành công, nó sẽ trả về giá trị mặc định.


3
điều này đang làm gì "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Có thể là một số phụ thuộc vào mã cục bộ của bạn.
Manish Basantani
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.