Bagaimana cara TryParse untuk nilai Enum?

94

Saya ingin menulis fungsi yang dapat memvalidasi nilai yang diberikan (diteruskan sebagai string) terhadap kemungkinan nilai dari sebuah enum. Dalam kasus pertandingan, itu harus mengembalikan contoh enum; jika tidak, itu harus mengembalikan nilai default.

Fungsi tersebut mungkin tidak digunakan secara internal try / secaracatch , yang mengecualikan penggunaan Enum.Parse, yang memunculkan pengecualian saat diberikan argumen yang tidak valid.

Saya ingin menggunakan sesuatu di sepanjang baris TryParsefungsi untuk mengimplementasikan ini:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}
Manish Basantani
sumber
8
Saya tidak mengerti pertanyaan ini; Anda berkata, "Saya ingin menyelesaikan masalah ini, tetapi saya tidak ingin menggunakan metode apa pun yang akan memberi saya solusi." Apa gunanya?
Domenic
1
Apa keengganan Anda untuk mencoba / menangkap solusi? Jika Anda mencoba menghindari Pengecualian karena 'mahal', mohon istirahat sejenak. Dalam 99% kasus, Biaya untuk membuang / menangkap pengecualian biaya dapat diabaikan dibandingkan dengan kode utama Anda.
SolutionYogi
1
Biaya penanganan pengecualian tidak terlalu buruk. Sial, implementasi internal dari semua konversi enumerasi ini penuh dengan penanganan pengecualian. Saya sangat tidak suka pengecualian dilemparkan dan ditangkap selama logika aplikasi normal. Terkadang berguna untuk menghentikan semua pengecualian yang dilempar (bahkan saat tertangkap). Melempar pengecualian di semua tempat akan membuatnya lebih menjengkelkan untuk digunakan :)
Thorarin
3
@ Domenic: Saya hanya mencari solusi yang lebih baik daripada yang sudah saya ketahui. Apakah Anda pernah pergi ke pertanyaan kereta api untuk menanyakan rute atau kereta api yang sudah Anda ketahui :).
Manish Basantani
2
@Amby, biaya untuk memasukkan blok coba / tangkap dapat diabaikan. Biaya untuk MELALUI pengecualian tidak, tapi itu seharusnya luar biasa, bukan? Selain itu, jangan katakan "kami tidak pernah tahu" ... tulis kode tersebut dan cari tahu. Jangan buang waktu Anda bertanya-tanya apakah ada sesuatu yang lambat, CARI TAHU!
akmad

Jawaban:

31

Seperti yang dikatakan orang lain, Anda harus menerapkannya sendiri TryParse. Simon Mourier menyediakan implementasi penuh yang menangani semuanya.

Jika Anda menggunakan enum bitfield (yaitu flags), Anda juga harus menangani string seperti "MyEnum.Val1|MyEnum.Val2"yang merupakan kombinasi dari dua nilai enum. Jika Anda hanya memanggil Enum.IsDefineddengan string ini, itu akan mengembalikan false, meskipun Enum.Parsemenanganinya dengan benar.

Memperbarui

Seperti yang disebutkan oleh Lisa dan Christian di komentar, Enum.TryParsesekarang tersedia untuk C # di .NET4 dan yang lebih baru.

MSDN Docs

Victor Arndt Mueller
sumber
Mungkin yang paling tidak seksi, tapi saya setuju ini pasti yang terbaik sampai kode Anda dipindahkan ke .NET 4.
Lisa
1
Seperti disebutkan di bawah, tetapi tidak terlalu terlihat: Mulai .Net 4 Enum.TryParse tersedia dan berfungsi tanpa pengkodean tambahan. Informasi lebih lanjut tersedia dari MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
106

Enum.IsDefined akan menyelesaikan berbagai hal. Ini mungkin tidak seefisien TryParse, tetapi ini akan bekerja tanpa penanganan pengecualian.

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

Perlu dicatat: TryParsemetode telah ditambahkan di .NET 4.0.

Thorarin
sumber
1
Jawaban terbaik yang pernah saya lihat sejauh ini ... tidak ada coba / tangkap, tidak ada GetNames :)
Thomas Levesque
13
Kekurangan Enum.IsDefined: blogs.msdn.com/brada/archive/2003/11/29/50903.aspx
Nader Shirazie
6
juga tidak ada kasus pengabaian pada IsDefined
Anthony Johnston
2
@Anthony: jika Anda ingin mendukung ketidakpekaan huruf besar / kecil, Anda perlu GetNames. Secara internal, semua metode ini (termasuk Parse) digunakan GetHashEntry, yang melakukan refleksi aktual - sekali. Sisi baiknya, .NET 4.0 memiliki TryParse, dan ini juga umum :)
Thorarin
+1 Ini menyelamatkan hari saya! Saya mem-backport banyak kode dari .NET 4 ke .NET 3.5 dan Anda menyelamatkan saya :)
daitangio
20

Berikut adalah implementasi khusus dari EnumTryParse. Tidak seperti implementasi umum lainnya, ini juga mendukung enum yang ditandai dengan Flagsatribut.

    /// <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;
    }
Simon Mourier
sumber
1
Anda memberikan penerapan terbaik dan saya telah menggunakannya untuk tujuan saya sendiri; Namun, saya bertanya-tanya mengapa Anda menggunakan Activator.CreateInstance(type)untuk membuat nilai enum default dan tidak Enum.ToObject(type, 0). Hanya masalah selera?
Pierre Arnaud
1
@Pierre - Hmmm ... tidak, saat itu sepertinya lebih alami :-) Mungkin Enum.ToObject lebih cepat karena secara internal menggunakan panggilan internal InternalBoxEnum? Aku tidak pernah memeriksanya ...
Simon Mourier
2
Seperti disebutkan di bawah, tetapi tidak terlalu terlihat: Mulai .Net 4 Enum.TryParse tersedia dan berfungsi tanpa pengkodean tambahan. Informasi lebih lanjut tersedia dari MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
16

Pada akhirnya Anda harus menerapkan ini di sekitar 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;
}

Catatan tambahan:

  • Enum.TryParsedisertakan dalam .NET 4. Lihat di sini http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Pendekatan lain adalah dengan langsung membungkus Enum.Parsepenangkapan pengecualian yang dilemparkan ketika gagal. Ini bisa lebih cepat saat kecocokan ditemukan, tetapi kemungkinan akan lebih lambat jika tidak. Bergantung pada data yang Anda proses, ini mungkin atau mungkin bukan peningkatan bersih.

EDIT: Baru saja melihat implementasi yang lebih baik untuk ini, yang menyimpan informasi yang diperlukan: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5

Richard
sumber
Saya akan menyarankan menggunakan default (T) untuk mengatur nilai default. Ternyata ini tidak akan berhasil untuk semua enum. Misalnya Jika tipe yang mendasari untuk enum adalah int default (T) akan selalu mengembalikan 0, yang mungkin atau mungkin tidak valid untuk enum.
Daniel Ballinger
Implementasi di blog Damieng tidak mendukung enum dengan Flagsatribut.
Uwe Keim
9

Berdasarkan .NET 4.5

Contoh kode di bawah ini

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

Referensi: http://www.dotnetperls.com/enum-parse

Hugo Hilário
sumber
4

Saya memiliki implementasi yang dioptimalkan yang dapat Anda gunakan di UnconstrainedMelody . Secara efektif itu hanya menyimpan daftar nama, tetapi melakukannya dengan cara yang bagus, diketik dengan kuat, dibatasi secara umum :)

Jon Skeet
sumber
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)) {

}
Everson Rafael
sumber
2

Saat ini tidak ada Enum.TryParse di luar kotak. Ini telah diminta di Connect ( Masih tidak ada Enum.TryParse ) dan mendapat respons yang menunjukkan kemungkinan penyertaan dalam kerangka kerja berikutnya setelah .NET 3.5. Anda harus menerapkan solusi yang disarankan untuk saat ini.

Ahmad Mageed
sumber
1

Satu-satunya cara untuk menghindari penanganan pengecualian adalah dengan menggunakan metode GetNames (), dan kita semua tahu bahwa pengecualian tidak boleh disalahgunakan untuk logika aplikasi umum :)

Philippe Leybaert
sumber
1
Ini bukan satu - satunya cara. Enum.IsDefined (..) akan mencegah pengecualian dilemparkan dalam kode pengguna.
Thorarin
1

Apakah caching sebuah fungsi / kamus yang dibuat secara dinamis diperbolehkan?

Karena Anda tidak (tampaknya) mengetahui jenis enum sebelumnya, eksekusi pertama dapat menghasilkan sesuatu yang dapat dimanfaatkan oleh eksekusi berikutnya.

Anda bahkan dapat menyimpan hasil Enum.GetNames () ke dalam cache

Apakah Anda mencoba mengoptimalkan CPU atau Memori? Apakah Anda benar - benar perlu?

Nader Shirazie
sumber
Ide untuk mengoptimalkan CPU. Setuju bahwa saya dapat melakukannya dengan memori biaya. Tapi itu bukan solusi yang saya cari. Terima kasih.
Manish Basantani
0

Seperti yang sudah dikatakan orang lain, jika Anda tidak menggunakan Try & Catch, Anda perlu menggunakan IsDefined atau GetNames ... Berikut adalah beberapa contoh ... pada dasarnya semuanya sama, yang pertama menangani enum nullable. Saya lebih suka yang ke-2 karena ini merupakan ekstensi pada string, bukan enum ... tetapi Anda dapat mencampurnya sesuai keinginan!

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

sumber
0

Tidak ada TryParse karena jenis Enum tidak diketahui hingga runtime. Sebuah TryParse yang mengikuti metodologi yang sama seperti misalnya metode Date.TryParse akan memunculkan kesalahan konversi implisit pada parameter ByRef.

Saya sarankan melakukan sesuatu seperti ini:

//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);
    }
}
ben
sumber
Untuk Trymetode yang hasilnya mungkin tipe nilai, atau di mana nullmungkin merupakan hasil yang sah (misalnya Dictionary.TryGetValue, which has both such traits), the normal pattern is for a metode Try` untuk mengembalikan bool, dan meneruskan hasilnya sebagai outparameter. Bagi mereka yang mengembalikan tipe kelas di mana nullbukan hasil yang valid, tidak ada kesulitan menggunakan nullpengembalian untuk menunjukkan kegagalan.
supercat
-1

Lihat kelas Enum (struct?) Itu sendiri. Ada metode Parse untuk itu tapi saya tidak yakin tentang tryparse.

Tempat menyimpan bahan makanan
sumber
Saya tahu tentang metode Enum.Parse (typeof (TEnum), strEnumValue). ArgumentException akan dilontarkan jika strEnumValue tidak valid. Mencari TryParse ........
Manish Basantani
-2

Metode ini akan mengonversi jenis 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());
    } 

Ia memeriksa jenis yang mendasari dan mendapatkan nama untuk mengurai. Jika semuanya gagal, itu akan mengembalikan nilai default.

Naveed Ahmed
sumber
3
apa yang dilakukan ini "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Mungkin beberapa ketergantungan pada kode lokal Anda.
Manish Basantani