EnumのTryParseって

値型にしろクラス型にしろ、そのデフォルト値を取得する為に、default キーワードが用意されている。このdefaultキーワード、ジェネリックにも使えたりするので、それなりに重宝している。
で、仕事でEnumに関してデフォルトを取得するメソッドを用意する事になった。
using System;

namespace TawamureDays {

/// <summary>
/// TawamureDays ユーティリティなメソッド用クラス
/// </summary>
/// <remarks>
/// ユーティリティ的なメソッドを提供するstaticなクラスです。<br/>
/// </remarks>
public static partial class Utils {

/// <summary>
/// 引数の数値をジェネリック指定のEnum値で取得します。<br/>
/// キャストすりゃ良いじゃん?というツッコミは無しで<br/>
/// </summary>
/// <param name="val">対象の値</param>
/// <param name="defaultValue">変換失敗時のデフォルト値</param>
/// <returns>変換後のEnum値</returns>
public static TEnum GetEnum<TEnum>(
int val,
TEnum defaultValue = default(TEnum))
where TEnum : struct {

var enumVal = default(TEnum);

if (Enum.TryParse<TEnum>(val.ToString(), out enumVal)) {
return enumVal;
}

return defaultValue;
}
}
}
一見上手く動くように思えるんだけど…。試しに実行したコードで嫌な結果が分かった。
enum EnumA : int {
One = 1,
Two = 2,
Three = 3
};
//...
var enumVal = Utils.GetEnum<EnumA>(4, EnumA.Two);
この結果で得られる値は、「4」である。

var enumVal1 = (EnumA)4;
この結果で得られる値も、「4」である。Castエラーも発生しない。

var enumVal2 = Utils.GetEnum<EnumA>("Four", EnumA.Two);
この結果で得られる値は、「Two」である。

なんとまあ、引数にint値を与えたら、TryParseってfalseを返さない事がわかった。
ということは、int値からEnumへ変換するとき、TryParseって意味がないという事になる。
後、第2引数(default値)を与えずに実行してみると、
var enumVal2 = Utils.GetEnum<EnumA>("Four");
この結果で得られる値は、「0」である。

Enum値に0を値に持つ定数が定義されていなくても、defaultキーワードで得られる値は0になる。
enum EnumA : int {
One = 1,
Two = 2,
Three = 3,
Zero = 0
};
みたいにしているとZeroが返される。
int値からEnumに変換するとき、下手にTryParseは使えないことがよくわかった。
なので、上記メソッドを改造する事になった。
using System;
using System.Linq;

namespace TawamureDays {

/// <summary>
/// TawamureDays ユーティリティなメソッド用クラス
/// </summary>
/// <remarks>
/// ユーティリティ的なメソッドを提供するstaticなクラスです。<br/>
/// </remarks>
public static partial class Utils {

/// <summary>
/// 引数の数値をジェネリック指定のEnum値で取得します。<br/>
/// キャストすりゃ良いじゃん?というツッコミは無しで<br/>
/// </summary>
/// <param name="val">対象の値</param>
/// <param name="defaultValue">変換失敗時のデフォルト値</param>
/// <returns>変換後のEnum値</returns>
public static TEnum GetEnum<TEnum>(
int val,
TEnum defaultValue = default(TEnum))
where TEnum : struct {

if (Enum.IsDefined(typeof(TEnum), val)) {
//定義されているのなら、Parseして返す
return (TEnum)Enum.Parse(typeof(TEnum), val.ToString());

} else if (Enum.IsDefined(typeof(TEnum), defaultValue)) {
//デフォルト値が定義内であればそのまま返す
return defaultValue;

} else {
//デフォルト値がEnum定義外(Enumに0を持つ定数がない)
//定義内の最小値を返す
return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().First();
}
}
}
}
これで、
var enumVal = Utils.GetEnum<EnumA>(4, EnumA.Two);
で得られる結果が「Two」になる。

var enumVal = Utils.GetEnum<EnumA>(4);
では得られる結果が「One」になる。

まあ、定義していない0になるよりはマシかな。先に書いたコードも、引数をintからstringにしてしまえば、使える。オーバーロードで持たせておけば、意識せずに使えるかな。しかし、キャストくらいエラーになるだろと思ったらならないとはな。
(追記)
EnumのTryParseの説明ページを検索してみた。
Enum.TryParse<TEnum> メソッド
さらにアレな動きが描かれていた。以下上記ページより引用

value パラメーターには、列挙体のメンバーの基になる値または名前付き定数の文字列形式、またはコンマ (,) で区切られた名前付き定数または基になる値の一覧が格納されます。 value に複数の名前付き定数または値が含まれている場合は、1 つ以上の空白スペースを value 内の各値、名前、またはコンマの前または後に配置できます。 value がリストの場合、result は、指定した名前または基になる値をビットごとの OR 演算で組み合わせた結果の値を反映します。

ん?コンマ?と思って、次のコードを実行してみた。
var enumVal3 = Utils.GetEnum<EnumA>("One,Two");
得られる結果は「Three」であった。うーん。Flag属性付のEnumでは使えるのかもしれない。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m