C#で文字列を列挙値に変換する最良の方法は何ですか?
列挙値を含む HTML select タグがあります。ページが投稿されたら、値 (文字列の形式になります) を取得し、対応する列挙値に変換したいと考えています。
理想的な世界では、次のようなことができます。
StatusEnum MyStatus = StatusEnum.Parse("Active");
しかし、それは有効なコードではありません。
.NET Core および .NET Framework ≥4.0には、汎用の解析メソッドがあります。
Enum.TryParse("Active", out StatusEnum myStatus);
これには C#7 の新しいインラインout
変数も含まれているため、try-parse を実行し、明示的な列挙型に変換し、myStatus
変数を初期化して入力します。
C#7 と最新の .NET にアクセスできる場合は、これが最善の方法です。
.NETではかなり醜いです(4以上まで):
StatusEnum MyStatus = (StatusEnum) Enum.Parse(typeof(StatusEnum), "Active", true);
私はこれを次のように単純化する傾向があります。
public static T ParseEnum<T>(string value)
{
return (T) Enum.Parse(typeof(T), value, true);
}
それから私はすることができます:
StatusEnum MyStatus = EnumUtil.ParseEnum<StatusEnum>("Active");
コメントで提案されているオプションの 1 つは、拡張機能を追加することです。これは非常に簡単です。
public static T ToEnum<T>(this string value)
{
return (T) Enum.Parse(typeof(T), value, true);
}
StatusEnum MyStatus = "Active".ToEnum<StatusEnum>();
最後に、文字列を解析できない場合に使用するデフォルトの列挙型が必要になる場合があります。
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;
}
これが呼び出しになります:
StatusEnum MyStatus = "Active".ToEnum(StatusEnum.None);
ただし、このような拡張メソッドstring
を (名前空間制御なしで)string
列挙型を保持しているかどうかのすべてのインスタンスに表示されるように追加することに注意します (1234.ToString().ToEnum(StatusEnum.None)
有効ですが無意味です)。開発チーム全体がこれらの拡張機能の機能を十分に理解している場合を除き、非常に特定のコンテキストでのみ適用される追加のメソッドで Microsoft のコア クラスを乱雑にしないことが最善の方法であることがよくあります。
使用Enum.TryParse<T>(String, T)
(≥ .NET 4.0):
StatusEnum myStatus;
Enum.TryParse("Active", out myStatus);
C# 7.0 のパラメーター型のインライン化を使用すると、さらに単純化できます。
Enum.TryParse("Active", out StatusEnum myStatus);
リフレクションを介して実装されているため、 のパフォーマンスEnum.Parse()
はひどいことに注意してください。(Enum.ToString
逆の場合も同様です。)
パフォーマンスが重要なコードで文字列を Enum に変換する必要がある場合、最善の策はDictionary<String,YourEnum>
、起動時に を作成し、それを使用して変換を行うことです。
Enum.Parseを探しています。
SomeEnum enum = (SomeEnum)Enum.Parse(typeof(SomeEnum), "EnumValue");
注意:
enum Example
{
One = 1,
Two = 2,
Three = 3
}
Enum.(Try)Parse()
複数のカンマ区切りの引数を受け入れ、それらをバイナリ 'or' で結合します|
。これを無効にすることはできません。私の意見では、これを無効にすることはほとんどありません。
var x = Enum.Parse("One,Two"); // x is now Three
Three
が定義されていなくてもx
、 int value を取得します3
。それはさらに悪いことです: Enum.Parse() は、列挙型に対して定義されていない値を与える可能性があります!
ユーザーが意図的または不本意に、この動作をトリガーした結果を経験したくありません。
さらに、他の人が述べたように、パフォーマンスは大きな列挙型、つまり可能な値の数が線形の場合には理想的ではありません。
次のことをお勧めします。
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);
}
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);
したがって、mood という名前の列挙型がある場合、次のようになります。
enum Mood
{
Angry,
Happy,
Sad
}
// ...
Mood m = (Mood) Enum.Parse(typeof(Mood), "Happy", true);
Console.WriteLine("My mood is: {0}", m.ToString());
Enum.Parseはあなたの友達です:
StatusEnum MyStatus = (StatusEnum)Enum.Parse(typeof(StatusEnum), "Active");
受け入れられた回答をデフォルト値で拡張して、例外を回避できます。
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;
}
}
次に、次のように呼び出します。
StatusEnum MyStatus = EnumUtil.ParseEnum("Active", StatusEnum.None);
デフォルト値が列挙型でない場合、Enum.TryParse は失敗し、キャッチされる例外をスローします。
コードの多くの場所でこの関数を何年も使用した後、この操作がパフォーマンスを犠牲にするという情報を追加するのは良いことかもしれません!
完全に有効な入力を想定することはできず、@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;
}
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str)
{
return (T) Enum.Parse(typeof(T), str);
}
これがいつ追加されたのかはわかりませんが、Enum クラスには現在
Parse<TEnum>(stringValue)
問題の例では次のように使用されます。
var MyStatus = Enum.Parse<StatusEnum >("Active")
または大文字と小文字を無視する:
var MyStatus = Enum.Parse<StatusEnum >("active", true)
これが使用する逆コンパイルされたメソッドは次のとおりです。
[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;
}
私は拡張メソッドソリューションが好きです..
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;
}
}
}
ここでは、テストを使用した実装を以下に示します。
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
}
ここでは、EnumMember 値を持つ列挙値のケースが考慮されていないことがわかりました。だからここに行きます:
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;
}
そしてその列挙型の例:
public enum OracleInstanceStatus
{
Unknown = -1,
Started = 1,
Mounted = 2,
Open = 3,
[EnumMember(Value = "OPEN MIGRATE")]
OpenMigrate = 4
}
public static T ParseEnum<T>(string value) //function declaration
{
return (T) Enum.Parse(typeof(T), value);
}
Importance imp = EnumUtil.ParseEnum<Importance>("Active"); //function call
====================完全なプログラム====================
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.
私はクラスを使用しました (解析とパフォーマンスが向上した Enum の厳密に型指定されたバージョン)。GitHub で見つけましたが、.NET 3.5 でも動作するはずです。ディクショナリをバッファリングするため、メモリ オーバーヘッドが発生します。
StatusEnum MyStatus = Enum<StatusEnum>.Parse("Active");
ブログ投稿はEnums – Better syntax, Improvement performance and TryParse in NET 3.5です。
コード: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/System/EnumT.cs
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
return defaultValue;
return Enum.Parse(typeof(TEnum), value, true);}
<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