有効な列挙値であるかどうかを知るために、整数を検証する必要があります。
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」を削除してください)
IMHO回答としてマークされた投稿は正しくありません。
パラメータとデータの検証は、何十年も前に掘り下げられたものの 1 つです。
どうして
エラーをスローすることなく、本質的に任意の整数値を列挙型に割り当てることができるため、検証が必要です。
多くの場合必要な機能であるため、C# の enum 検証の研究に何日も費やしました。
どこ
私にとっての列挙型検証の主な目的は、ファイルから読み取ったデータを検証することです。ファイルが破損しているか、外部から変更されているか、意図的にハッキングされているかどうかはわかりません。
また、クリップボードから貼り付けられたアプリケーション データの enum 検証では、ユーザーがクリップボードの内容を編集したかどうかはわかりません。
そうは言っても、私は見つけたり設計したりできるすべてのメソッドのパフォーマンスをプロファイリングするなど、多くのメソッドの調査とテストに何日も費やしました.
System.Enum 内の呼び出しは非常に遅いため、境界を検証する必要のあるプロパティに 1 つ以上の列挙型を持つオブジェクトが数百または数千含まれている関数では、パフォーマンスが著しく低下しました。
要するに、列挙値を検証するときは System.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;
}
パフォーマンス
興味のある方のために、enum 検証で次のバリエーションをプロファイリングしました。結果は次のとおりです。
プロファイリングは、ランダムな整数入力値を使用して、各メソッドで 100 万回のループでリリース コンパイル時に実行されました。各テストは 10 回以上実行され、平均化されました。ティックの結果には、乱数の生成などを含む合計実行時間が含まれますが、それらはテスト全体で一定です。1 ティック = 10ns。
ここのコードは完全なテスト コードではなく、基本的な enum 検証方法であることに注意してください。テストされたこれらの追加のバリエーションも多数あり、それらのすべてで、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 ticks
注: このコード バージョンは最小/最大に固定されませんが、範囲外の場合は 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;
}
-eol-
他の人が述べたように、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
}
Brad AbramsEnum.IsDefined
は、自身の投稿The Danger of Oversimplificationで具体的に警告しています。
この要件 (つまり、列挙型を検証する必要性) を取り除く最善の方法は、ユーザーが間違える可能性のある方法 (たとえば、ある種の入力ボックス) を取り除くことです。たとえば、列挙型とドロップダウンを使用して、有効な列挙型のみを適用します。
この回答は、System.Enum のパフォーマンスの問題を引き起こす deegee の回答に対応するものであるため、私の優先する一般的な回答と見なすべきではありません。パフォーマンスの厳しいシナリオで列挙型の検証に対処する必要があります。
遅いが機能的なコードがタイトなループで実行されているというミッション クリティカルなパフォーマンスの問題がある場合、個人的には、機能を減らして解決するのではなく、可能であればそのコードをループの外に移動することを検討します。連続した列挙のみをサポートするようにコードを制限すると、たとえば、将来誰かがいくつかの列挙値を非推奨にすることを決定した場合、バグを見つけるのは悪夢になる可能性があります。簡単に言えば、最初に Enum.GetValues を 1 回呼び出すだけで、すべてのリフレクションなどを何千回もトリガーすることを避けることができます。これにより、すぐにパフォーマンスが向上するはずです。より多くのパフォーマンスが必要で、多くの列挙型が連続していることがわかっている場合 (ただし、「gappy」列挙型をサポートしたい場合)、さらに段階を進めて次のようなことを行うことができます。
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);
}
}
値が列挙内の有効な値であるかどうかを検証するには、静的メソッド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
}
私はそれに非常によく答えるこのリンクを見つけました。それは使用しています:
(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)