C# でフラグを比較するにはどうすればよいですか?
-
09-06-2019 - |
質問
以下にフラグ列挙型があります。
[Flags]
public enum FlagTest
{
None = 0x0,
Flag1 = 0x1,
Flag2 = 0x2,
Flag3 = 0x4
}
if ステートメントを true と評価させることができません。
FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;
if (testItem == FlagTest.Flag1)
{
// Do something,
// however This is never true.
}
どうすればこれを真実にできるでしょうか?
解決
.NET 4には新しいメソッドがあります Enum.HasFlag. 。これにより、次のように書くことができます。
if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
// Do Stuff
}
これははるかに読みやすいです、IMO。
.NET ソースは、これが受け入れられた回答と同じロジックを実行することを示しています。
public Boolean HasFlag(Enum flag) {
if (!this.GetType().IsEquivalentTo(flag.GetType())) {
throw new ArgumentException(
Environment.GetResourceString(
"Argument_EnumTypeDoesNotMatch",
flag.GetType(),
this.GetType()));
}
ulong uFlag = ToUInt64(flag.GetValue());
ulong uThis = ToUInt64(GetValue());
// test predicate
return ((uThis & uFlag) == uFlag);
}
他のヒント
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do something
}
(testItem & FlagTest.Flag1)
はビット単位の AND 演算です。
FlagTest.Flag1
と同等です 001
OPの列挙型を使用します。さあ、言ってみましょう testItem
Flag1 と Flag2 があります (つまりビット単位です) 101
):
001
&101
----
001 == FlagTest.Flag1
受け入れられているソリューション(これです)で何が起こっているかを視覚化するのに苦労している人のために、
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do stuff.
}
testItem
(質問に従って)は次のように定義されます、
testItem
= flag1 | flag2
= 001 | 010
= 011
次に、if ステートメントでは、比較の左側は次のようになります。
(testItem & flag1)
= (011 & 001)
= 001
そして、完全な if ステートメント (if は true と評価されます) flag1
に設定されています testItem
),
(testItem & flag1) == flag1
= (001) == 001
= true
@phil-devaney
最も単純な場合を除いて、 Enum.HasFlag コードを手動で記述する場合と比較して、パフォーマンスに大きなペナルティが伴います。次のコードを考えてみましょう。
[Flags]
public enum TestFlags
{
One = 1,
Two = 2,
Three = 4,
Four = 8,
Five = 16,
Six = 32,
Seven = 64,
Eight = 128,
Nine = 256,
Ten = 512
}
class Program
{
static void Main(string[] args)
{
TestFlags f = TestFlags.Five; /* or any other enum */
bool result = false;
Stopwatch s = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
result |= f.HasFlag(TestFlags.Three);
}
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*
s.Restart();
for (int i = 0; i < 10000000; i++)
{
result |= (f & TestFlags.Three) != 0;
}
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*
Console.ReadLine();
}
}
1,000 万回の反復を超えると、標準のビットごとの実装の 27 ミリ秒と比較して、HasFlags 拡張メソッドでは 4793 ミリ秒かかります。
それを行うための拡張メソッドを設定しました。 関連する質問.
基本的に:
public static bool IsSet( this Enum input, Enum matchTo )
{
return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
次に、次のことができます。
FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;
if( testItem.IsSet ( FlagTests.Flag1 ) )
//Flag1 is set
ちなみに、私が列挙型に使用する規則は、標準の場合は単数形、フラグの場合は複数形です。そうすることで、列挙名から複数の値を保持できるかどうかがわかります。
もう一つアドバイスが…値が「0」のフラグを使用して標準バイナリ チェックを実行しないでください。このフラグのチェックは常に true になります。
[Flags]
public enum LevelOfDetail
{
[EnumMember(Value = "FullInfo")]
FullInfo=0,
[EnumMember(Value = "BusinessData")]
BusinessData=1
}
FullInfo に対して入力パラメータをバイナリ チェックすると、次の結果が得られます。
detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;
ANYTHING & 0 は常に == 0 であるため、bPRez は常に true になります。
代わりに、入力の値が 0 であることを単純に確認する必要があります。
bool bPRez = (detailLevel == LevelOfDetail.FullInfo);
if((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
...
}
ビット演算の場合は、ビットごとの演算子を使用する必要があります。
これでうまくいくはずです:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do something,
// however This is never true.
}
編集: if チェックを修正しました - C/C++ のやり方に戻りました (指摘してくれた Ryan Farley に感謝します)
編集に関して。それを真実にすることはできません。必要な構文に近づけるために、必要なものを別のクラス (または拡張メソッド) にラップすることをお勧めします。
つまり
public class FlagTestCompare
{
public static bool Compare(this FlagTest myFlag, FlagTest condition)
{
return ((myFlag & condition) == condition);
}
}
これを試して:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// do something
}
基本的に、コードは両方のフラグを設定することが 1 つのフラグを設定することと同じかどうかを尋ねていますが、これは明らかに false です。上記のコードは、Flag1 ビットが設定されている場合のみ設定したままにして、この結果を Flag1 と比較します。
[Flags] がなくても、次のようなものを使用できます
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}
またはゼロ値列挙型がある場合
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}