質問
整数が有効な列挙値であるかどうかを確認するには、整数を検証する必要があります。
C# でこれを行う最良の方法は何ですか?
解決
データは常に UI から取得されるだけでなく、自分が制御できる UI からもたらされると考える人々を、あなたは愛さなければなりません。
IsDefined
ほとんどのシナリオでは問題ありませんが、以下から始めることができます。
public static bool TryParseEnum<TEnum>(this int enumValue, out TEnum retVal)
{
retVal = default(TEnum);
bool success = Enum.IsDefined(typeof(TEnum), enumValue);
if (success)
{
retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue);
}
return success;
}
(適切な int 拡張子ではないと思われる場合は、明らかに「this」を削除してください)
他のヒント
私の意見では、回答としてマークされた投稿は間違っています。
パラメーターとデータの検証は、数十年前に私に叩き込まれたものの 1 つです。
なぜ
基本的にエラーをスローせずに任意の整数値を列挙型に割り当てることができるため、検証が必要です。
C# の列挙型検証は多くの場合に必要な機能であるため、私は何日もかけて研究しました。
どこ
私にとって列挙型検証の主な目的は、ファイルから読み取られたデータを検証することです。ファイルが破損しているのか、外部から変更されたのか、それとも意図的にハッキングされたのかはわかりません。
そして、クリップボードから貼り付けられたアプリケーション データの enum 検証を使用します。ユーザーがクリップボードの内容を編集したかどうかはわかりません。
そうは言っても、私は発見または設計できるすべてのメソッドのパフォーマンスのプロファイリングを含む、多くのメソッドの研究とテストに何日も費やしました。
System.Enum 内の呼び出しは非常に遅いため、境界を検証する必要があるプロパティに 1 つ以上の列挙型を持つ数百または数千のオブジェクトを含む関数では、顕著なパフォーマンスの低下が見られました。
結論から言うと、近づかないでください すべて System.Enum クラスで enum 値を検証するとき、恐ろしく遅いです。
結果
私が現在 enum 検証に使用している方法は、おそらく多くのプログラマから注目を集めるでしょうが、私の特定のアプリケーション設計にとっては、これが最も悪影響が少ないと考えられます。
enum の上限と (オプションで) 下限である 1 つまたは 2 つの定数を定義し、それらを検証のために 1 組の if() ステートメントで使用します。
欠点の 1 つは、列挙型を変更する場合は定数を必ず更新する必要があることです。
このメソッドは、列挙型が「自動」スタイルであり、各列挙型要素が 0、1、2、3、4、... などの増分整数値である場合にのみ機能します。インクリメンタルではない値を持つフラグまたは列挙型では正しく動作しません。
また、このメソッドは、通常の int32 の "<" ">" の場合とほぼ同じ速度であることに注意してください (私のテストでは 38,000 ティックを記録しました)。
例えば:
public const MyEnum MYENUM_MINIMUM = MyEnum.One;
public const MyEnum MYENUM_MAXIMUM = MyEnum.Four;
public enum MyEnum
{
One,
Two,
Three,
Four
};
public static MyEnum Validate(MyEnum value)
{
if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
return value;
}
パフォーマンス
興味のある方のために、列挙型検証に関する次のバリエーションをプロファイリングしました。結果は次のとおりです。
プロファイリングは、ランダムな整数入力値を使用して各メソッドに対して 100 万回のループでリリース コンパイル時に実行されました。各テストは 10 回以上実行され、平均されました。ティック結果には、乱数の生成などを含む実行にかかる合計時間が含まれます。ただし、それらはテスト全体にわたって一定です。1 ティック = 10ns。
ここのコードは完全なテスト コードではなく、基本的な列挙型検証メソッドにすぎないことに注意してください。これらにはさらに多くのバリエーションがテストされており、そのすべてが 1,800,000 ティックのベンチでここに示したものと同様の結果をもたらしました。
最も遅いものから最も速いものまで、四捨五入された結果でリストされています。タイプミスがないことを願っています。
メソッドで決定された境界 = 13,600,000 ティック
public static T Clamp<T>(T value)
{
int minimum = Enum.GetValues(typeof(T)).GetLowerBound(0);
int maximum = Enum.GetValues(typeof(T)).GetUpperBound(0);
if (Convert.ToInt32(value) < minimum) { return (T)Enum.ToObject(typeof(T), minimum); }
if (Convert.ToInt32(value) > maximum) { return (T)Enum.ToObject(typeof(T), maximum); }
return value;
}
Enum.IsDefined = 1,800,000 ティック
注記:このコード バージョンは Min/Max にクランプしませんが、範囲外の場合は Default を返します。
public static T ValidateItem<T>(T eEnumItem)
{
if (Enum.IsDefined(typeof(T), eEnumItem) == true)
return eEnumItem;
else
return default(T);
}
System.Enum キャストを使用して Int32 を変換する = 1,800,000 ティック
public static Enum Clamp(this Enum value, Enum minimum, Enum maximum)
{
if (Convert.ToInt32(value) < Convert.ToInt32(minimum)) { return minimum; }
if (Convert.ToInt32(value) > Convert.ToInt32(maximum)) { return maximum; }
return value;
}
if() の最小/最大定数 = 43,000 ティック = 42 倍と 316 倍速く勝者。
public static MyEnum Clamp(MyEnum value)
{
if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
return value;
}
-エル-
他の人も述べているように、 Enum.IsDefined
は遅いので、ループ内にある場合は注意が必要です。
複数の比較を行う場合、より迅速な方法は、最初に値を HashSet
. 。次に、単に使用します Contains
次のように、値が有効かどうかを確認します。
int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet<int> validVals = new HashSet<int>((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
// is valid
}
ブラッド・エイブラムスは特に次のように警告している Enum.IsDefined
彼の投稿で 過度の単純化の危険性.
この要件 (つまり、列挙型を検証する必要性) を取り除く最善の方法は、ユーザーが誤解する可能性のある方法 (ある種の入力ボックスなど) を削除することです。たとえば、有効な列挙型のみを強制するには、ドロップダウンで列挙型を使用します。
この回答は、System.Enum のパフォーマンスの問題を提起する deegee の回答に応じたものであるため、パフォーマンスが厳しいシナリオでの enum 検証にさらに対処する、私の好ましい一般的な回答として受け取るべきではありません。
遅いが機能するコードがタイトなループ内で実行されるというミッションクリティカルなパフォーマンスの問題がある場合、私個人としては、機能を減らすことで解決するのではなく、可能であればそのコードをループの外に移動することを検討します。連続した列挙型のみをサポートするようにコードを制約すると、たとえば将来誰かが一部の列挙型値を非推奨にすることを決定した場合にバグが見つかる悪夢となる可能性があります。単純化すると、最初に Enum.GetValues を 1 回呼び出すだけで、すべてのリフレクションなどが何千回もトリガーされるのを避けることができます。これにより、すぐにパフォーマンスが向上するはずです。さらなるパフォーマンスが必要で、列挙型の多くが連続していることがわかっている場合 (それでも「ガッピー」列挙型をサポートしたい場合)、さらに一歩進んで次のようなことを行うこともできます。
public abstract class EnumValidator<TEnum> where TEnum : struct, IConvertible
{
protected static bool IsContiguous
{
get
{
int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();
int lowest = enumVals.OrderBy(i => i).First();
int highest = enumVals.OrderByDescending(i => i).First();
return !Enumerable.Range(lowest, highest).Except(enumVals).Any();
}
}
public static EnumValidator<TEnum> Create()
{
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("Please use an enum!");
}
return IsContiguous ? (EnumValidator<TEnum>)new ContiguousEnumValidator<TEnum>() : new JumbledEnumValidator<TEnum>();
}
public abstract bool IsValid(int value);
}
public class JumbledEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
private readonly int[] _values;
public JumbledEnumValidator()
{
_values = Enum.GetValues(typeof (TEnum)).Cast<int>().ToArray();
}
public override bool IsValid(int value)
{
return _values.Contains(value);
}
}
public class ContiguousEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
private readonly int _highest;
private readonly int _lowest;
public ContiguousEnumValidator()
{
List<int> enumVals = Enum.GetValues(typeof (TEnum)).Cast<int>().ToList();
_lowest = enumVals.OrderBy(i => i).First();
_highest = enumVals.OrderByDescending(i => i).First();
}
public override bool IsValid(int value)
{
return value >= _lowest && value <= _highest;
}
}
ループは次のようになります。
//Pre import-loop
EnumValidator< MyEnum > enumValidator = EnumValidator< MyEnum >.Create();
while(import) //Tight RT loop.
{
bool isValid = enumValidator.IsValid(theValue);
}
EnumValidator クラスはもっと効率的に記述できると確信しています (これは簡単に説明するためのハックにすぎません) が、率直に言って、インポート ループの外側で何が起こるかなんて誰が気にするでしょうか。超高速である必要がある唯一のビットはループ内です。これが、ループ内での不要な if-enumContiguous-then-else を避けるために、抽象クラスのルートを採用した理由です (ファクトリ Create は基本的にこれを事前に実行します)。少し偽善的であることに気づくでしょう。簡潔にするために、このコードは機能を int-enum に制限しています。int を直接使用するのではなく、IConvertible を使用する必要がありますが、この答えはすでに十分に冗長です。
これは、オンラインの複数の投稿に基づいて私が行う方法です。これを行う理由は、列挙型が でマークされていることを確認するためです。 Flags
属性も正常に検証できます。
public static TEnum ParseEnum<TEnum>(string valueString, string parameterName = null)
{
var parsed = (TEnum)Enum.Parse(typeof(TEnum), valueString, true);
decimal d;
if (!decimal.TryParse(parsed.ToString(), out d))
{
return parsed;
}
if (!string.IsNullOrEmpty(parameterName))
{
throw new ArgumentException(string.Format("Bad parameter value. Name: {0}, value: {1}", parameterName, valueString), parameterName);
}
else
{
throw new ArgumentException("Bad value. Value: " + valueString);
}
}
私はこれを見つけました リンク それは非常にうまく答えています。それは使用しています:
(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)
私は、パフォーマンス、利便性、保守性を兼ね備えたこのソリューションに落ち着きました。
public enum DogBreed
{
Unknown = 0,
Beagle = 1,
Labrador = 2,
PeruvianIncaOrchid = 3,
}
public static class DogBreedExtensions
{
public static bool IsValidDogBreed(this DogBreed breed)
{
var v = (int)breed;
return v >= 1 && v <= 3;
}
}
そしてそれを次のように使用します。
var goodInput = 2;
var goodDog = (DogBreed)goodInput;
goodDog.IsValidDogBreed(); // true.
var badInput = 7;
var badDog = (DogBreed)badInput; // no problem, bad data here we come.
badDog.IsValidDogBreed(); // false, you're in the doghouse!
私の場合、通常は「Unknown」列挙値を拒否したいため、Enum.IsDefined を呼び出しても途中までしか取得できません。
このアプローチは、Enum.IsDefined のパフォーマンスの問題を回避し、列挙型に近い検証ルールを定義するので、これが気に入っています。
この拡張メソッドは、要件の変化に簡単に適応でき、必要に応じて int に適用できます (つまり、 public static bool IsValidDogBreed(this int breed)
)
これは、静的に構築されたメソッドを使用した、高速な汎用ソリューションです。 HashSet<T>
.
これをツールボックスで一度定義すると、その後はすべての列挙型検証に使用できます。
public static class EnumHelpers
{
/// <summary>
/// Returns whether the given enum value is a defined value for its type.
/// Throws if the type parameter is not an enum type.
/// </summary>
public static bool IsDefined<T>(T enumValue)
{
if (typeof(T).BaseType != typeof(System.Enum)) throw new ArgumentException($"{nameof(T)} must be an enum type.");
return EnumValueCache<T>.DefinedValues.Contains(enumValue);
}
/// <summary>
/// Statically caches each defined value for each enum type for which this class is accessed.
/// Uses the fact that static things exist separately for each distinct type parameter.
/// </summary>
internal static class EnumValueCache<T>
{
public static HashSet<T> DefinedValues { get; }
static EnumValueCache()
{
if (typeof(T).BaseType != typeof(System.Enum)) throw new Exception($"{nameof(T)} must be an enum type.");
DefinedValues = new HashSet<T>((T[])System.Enum.GetValues(typeof(T)));
}
}
}
このアプローチは、(大文字と小文字の区別と数値文字列表現に注意して) 文字列キーを持つ辞書を使用することで、列挙型解析にも簡単に拡張できることに注意してください。
値が列挙内の有効な値であるかどうかを検証するには、静的メソッドを呼び出すだけです。 Enum.IsDefined.
int value = 99;//Your int value
if (Enum.IsDefined(typeof(your_enum_type), value))
{
//Todo when value is valid
}else{
//Todo when value is not valid
}