C#でフラグを比較する方法は?


155

以下にフラグ列挙型があります。

[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.
}

どうすればこれを実現できますか?


私が間違っている場合は修正してください。フラグ値として使用するのに0は適切ですか?
ロイ・リー

4
@Roylee:0は許容範囲であり、フラグが設定されていないことをテストするために、「なし」または「未定義」フラグを設定することをお勧めします。必須ではありませんが、良い方法です。これについて覚えておくべき重要なことは、レオニードの答えで指摘されています。
Andy

5
@Roylee実際にはNone、ゼロの値を持つフラグを提供することがMicrosoftによって推奨されています。msdn.microsoft.com/en-us/library/vstudio/…を
ThatMatthew 2013

多くの人々はまた、ビット比較がそうあなただけcollection.containsフラグを行うことができますフラグのコレクション、の賛成で避けるべきで読み取ることが非常に困難であると主張
MikeT

あなたはあなたのロジックを反転する必要がある以外は、ビット単位の必要な、非常に接近していた&比較のための演算子を、|ほかのようなものです:1|2=35|2=73&2=27&2=28&2=00はにfalse、それ以外はに評価されますtrue
Damian Vogel

回答:


321

.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); 
}

23
ああ、ついに箱から出して何かを。これは素晴らしいことです。私はこの比較的単純な機能を長い間待っていました。。彼らはそれをスリップすることを決めグラッド
ロブ・バンGroenewoud

9
ただし、この方法のパフォーマンスの問題を示す以下の回答に注意してください-これは一部の人にとって問題になる可能性があります。幸いにも私にとってはありません。
アンディモーティマー

2
このメソッドは引数をEnumクラスのインスタンスとして受け取るため、このメソッドのパフォーマンスに関する考慮事項はボクシングです。
アダムホールズワース

1
パフォーマンスの問題に関する情報については、この答えを見てください:stackoverflow.com/q/7368652/200443
Maxence

180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) ビットごとのAND演算です。

FlagTest.Flag1001OPの列挙型と同等です。次に、testItemFlag1とFlag2があるとしましょう(ビット単位です101):

  001
 &101
 ----
  001 == FlagTest.Flag1

2
ここのロジックは正確には何ですか?なぜ述語はこのように書かなければならないのですか?
Ian R. O'Brien 14年

4
@ IanR.O'Brien Flag1 | フラグ2は、011と同じ001または010に変換されます。この場合、その011 == Flag1または変換された011 == 001の等号を実行すると、常にfalseが返されます。次に、Flag1とビット単位のANDを実行すると、011 AND 001に変換され、001を返すようになります
。001

HasFlagsはより多くのリソースを消費するため、これが最良のソリューションです。さらに、HasFlagsが実行しているすべての処理は、コンパイラによって行われます
Sebastian XaweryWiśniowiecki15年

78

受け入れられた解決策(これ)で何が起こっているかを視覚化することに問題がある人のために、

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (質問通り)は、

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

次に、ifステートメントで、比較の左側は、

(testItem & flag1) 
 = (011 & 001) 
 = 001

そして、完全なifステートメント(flag1がに設定されている場合にtrueと評価されますtestItem)、

(testItem & flag1) == flag1
 = (001) == 001
 = true

25

@ 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();
    }
}

1000万回以上の反復で、HasFlags拡張メソッドは、標準のビットごとの実装の27ミリ秒と比較して、なんと4793ミリ秒かかります。


5
確かに。HasFlagの実装を見ると、両方のオペランドで "GetType()"を実行していることがわかります。これは非常に低速です。次に、「Enum.ToUInt64(value.GetValue());」を実行します ビットごとのチェックを行う前に、両方のオペランドで。
user276648 '26

1
私はあなたのテストを数回実行し、HasFlagsで〜500ms、ビット単位で〜32msを得ました。ビット単位ではまだ1桁高速ですが、HasFlagsはテストから1桁下がっていました。(2.5GHz Core i3および.NET 4.5でテストを実行しました)
MarioVW 2013年

1
@MarioVW .NET 4で数回実行すると、i7-3770は、AnyCPU(64ビット)モードで〜2400ms vs〜20ms、32ビットモードで〜3000ms vs〜20msになります。.NET 4.5はわずかに最適化している場合があります。また、64ビットと32ビットのビルドのパフォーマンスの違いにも注意してください。これは、64ビット演算が高速化したことが原因である可能性があります(最初のコメントを参照)。
ボブ

1
(«flag var» & «flag value») != 0うまくいきません。条件が常に失敗し、私のコンパイラ(Unity3DのMono 2.6.5)がで使用された場合、「警告CS0162:到達不能コードが検出されました」と報告しif (…)ます。
Slipp D. Thompson、2015

1
@ wraith808:私のテストで間違いがあったのはあなたが正しいことだと気づき… = 1, … = 2, … = 4ました。列挙値の2の累乗はを使用する場合に非常に重要[Flags]です。私はそれがエントリーを1自動的に開始し、Po2sによって前進すると想定していました。動作は、MS .NETとUnityの日付の付いたMono全体で一貫しています。これをお詫び申し上げます。
Slipp D. Thompson、2015

21

私はそれを行うための拡張メソッドを設定しました:関連する質問

基本的に:

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

ちなみに、列挙型に使用する規則は、標準の場合は単数、フラグの場合は複数です。これにより、列挙名から複数の値を保持できるかどうかがわかります。


これはコメントであるべきですが、私は新しいユーザーなので、まだコメントを追加できないようです... public static bool IsSet(this Enum input、Enum matchTo){return(Convert.ToUInt32(input)&Convert .ToUInt32(matchTo))!= 0; }あらゆる種類の列挙型と互換性のある方法はありますか?
user276648 '19

これは、Enum.HasFlag(Enum)(.net 4.0で利用可能)でかなり冗長です
PPC

1
@PPC厳密に冗長だとは言えません-多くの人が古いバージョンのフレームワークで開発しています。あなたは正しいですが、.Net 4ユーザーはHasFlag代わりに拡張機能を使用する必要があります。
キース

4
@キース:また、注目すべき違いがあります:((FlagTest)0x1).HasFlag(0x0)はtrueを返しますが、これは望ましい動作である場合とそうでない場合があります
PPC

19

もう1つのアドバイス...値が「0」のフラグを使用して標準のバイナリチェックを行わないでください。このフラグのチェックは常に真になります。

[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;

bPRezは常に何でも&0として常にtrue == 0になります。


代わりに、入力の値が0であることを確認するだけです。

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);

そのような0フラグのバグを修正しました。これをテストする前に、どのフラグ値が0であるかを知る必要があるため、これは.NETフレームワーク(3.5)の設計エラーだと思います。
thersch


5

ビット演算では、ビットごとの演算子を使用する必要があります。

これでうまくいくはずです:

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

編集: ifチェックを修正しました-C / C ++の方法に戻りました(指摘してくれたRyan Farleyに感謝します)


5

編集に関して。あなたはそれを真実にすることはできません。必要な構文に近づけるために、必要なものを別のクラス(または拡張メソッド)にラップすることをお勧めします。

すなわち

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}

4

これを試して:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
基本的に、両方のフラグを設定することは、明らかにfalseである1つのフラグを設定することと同じかどうかをコードが尋ねています。上記のコードは、Flag1ビットが設定されている場合にのみ、それを設定したままにし、この結果をFlag1と比較します。


1

[フラグ]がなくても、このようなものを使うことができます

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

または、ゼロ値の列挙型がある場合

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.