列挙型の最も一般的なC#ビット演算


201

私の人生では、ビットフィールドのビットを設定、削除、切り替え、またはテストする方法を思い出せません。わからないか、めったに必要ないので、混同しています。したがって、「ビットチートシート」があればよいでしょう。

例えば:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

または

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

他のすべての一般的な操作の例を、できれば[Flags]列挙型を使用したC#構文で提供できますか?



7
このトピックの質問のヒントにリンクが表示されないほど残念です。
cori 2008

10
ただし、この質問にはc / c ++のタグが付けられているため、構文が同じであるように見えても、C#に関する情報を検索している人はおそらくそこを見ることはありません。
Adam Lassek、

私はビットテストを行うためのそれほど冗長ではない方法を知りません
Andy Johnson

2
@Andy、現在.NET 4のビットテスト用のAPIがあります。
Drew Noakes、

回答:


288

私はこれらの拡張機能についてさらに作業をしました- ここでコードを見つけることができます

私は頻繁に使用するSystem.Enumを拡張するいくつかの拡張メソッドを書きました...私はそれらが防弾であるとは主張していませんが、助けになりました... コメントは削除されました...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

次に、次のように使用されます

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false

1
私はこれも便利だと思いました-基になる型で機能するように変更する方法はありますか?
チャーリーソルト

7
これらの拡張機能は、私の日、私の週、私の月、そしておそらく私の年を作っただけです。
thaBadDawg

ありがとうございました!皆さん:Hugowareがリンクしているアップデートを必ずチェックしてください。
Helge Klein

非常に優れた拡張機能のセット。ボクシングが必要なのは残念ですが、ボクシングを使用しない代替案は考えられず、これは簡潔です。新しいHasFlag方法でもEnumボクシングが必要です。
Drew Noakes、

4
@Drew:ボクシングを回避する方法についてはcode.google.com/p/unconstrained-melodyを参照してください:)
Jon Skeet

109

.NET 4では、次のように記述できます。

flags.HasFlag(FlagsEnum.Bit4)

4
FlagsEnum醜い名前ですが、それを指摘するための+1 。:)
ジムシューベルト

2
@ジム、たぶん。これは、元の質問で使用されているサンプル名にすぎないため、コードで自由に変更できます。
Drew Noakes、2011

14
知っている!しかし、醜い名前はIE6のようなものであり、おそらく消えることはないでしょう:(
ジム・シューバート

5
@JimSchubertも、問題を混乱させないように、元の質問の型名を再現しました。.NET列挙型の命名ガイドラインは、すべてのことを示している[Flags]列挙型は、名前をpluralisedているはずなので、名前はFlagsEnum醜さよりもさらに深刻な問題を持っています。
Drew Noakes 2013年

1
また、フレームワーク設計ガイドライン:再利用可能な.NETライブラリの規則、慣用句、およびパターンをお勧めします。購入は少し高額ですが、Safari OnlineとBooks24x7の両方が購読者に提供していると思います。
ジムシューベルト

89

イディオムは、ビット単位の等号演算子を使用してビットを設定することです。

flags |= 0x04;

ビットをクリアするには、イディオムはビット単位で否定を使用することです。

flags &= ~0x04;

時々あなたはあなたのビットを特定するオフセットを持っている、そしてそれからイディオムはこれらを左シフトと組み合わせて使うことである:

flags |= 1 << offset;
flags &= ~(1 << offset);

22

@ドリュー

最も単純な場合を除いて、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ミリ秒かかります。


10
確かに興味深いと指摘するのが良いです。使用方法を考慮する必要があります。これによると、数十万以上の操作を実行していない場合は、おそらくこれに気付くこともないでしょう。
Joshua Hayes

7
このHasFlag方法には、この違いを説明するボックス化/ボックス化解除が含まれます。しかし、コストはごくわずか(0.4µs)なので、タイトなループに陥らない限り、私はいつでもより読みやすい(バグが少ない)宣言型API呼び出しを使用します。
Drew Noakes 2012年

8
使用方法によっては、問題になる可能性があります。そして私はローダーをかなり使っているので、指摘するのは良いことだと思いました。
チャックディー

11

.NETの組み込みフラグ列挙型操作は、残念ながら非常に制限されています。ほとんどの場合、ユーザーはビット単位の操作ロジックを理解する必要があります。

.NET 4では、ユーザーのコードを簡略化する方法HasFlagが追加されましたEnumが、残念ながら多くの問題があります。

  1. HasFlag 与えられた列挙型だけでなく、任意の型の列挙値引数を受け入れるため、タイプセーフではありません。
  2. HasFlag値がenum value引数によって提供されるフラグのすべてまたは一部を持っているかどうかをチェックするかどうかについては、あいまいです。ちなみにそれはすべてです。
  3. HasFlag 割り当てを引き起こし、ガベージコレクションを増やすボクシングを必要とするため、かなり遅いです。

フラグ列挙型に対する.NETの限定的なサポートが一部であるため、私はOSSライブラリEnums.NETを作成しました。

以下は、.NETフレームワークのみを使用した同等の実装とともに提供される操作の一部です。

フラグを組み合わせる

。ネット             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


フラグを削除

。ネット             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


一般的なフラグ

。ネット             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


フラグの切り替え

。ネット             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


すべてのフラグがあります

.NET             (flags & otherFlags) == otherFlagsまたはflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


フラグがあります

。ネット             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


フラグを取得

。ネット

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


これらの改善点を.NET Coreに組み込み、最終的には完全な.NET Frameworkを組み込むことを目指しています。ここで私の提案をチェックできます


7

C ++構文、ビット0がLSBであると想定、フラグが符号なしlongであると想定:

設定されているかどうかを確認します。

flags & (1UL << (bit to test# - 1))

設定されていないか確認してください:

invert test !(flag & (...))

セットする:

flag |= (1UL << (bit to set# - 1))

晴れ:

flag &= ~(1UL << (bit to clear# - 1))

トグル:

flag ^= (1UL << (bit to set# - 1))

3

最高のパフォーマンスとガベージゼロのために、これを使用してください:

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}

2

ビットをテストするには、次のようにします(フラグが32ビットの数値であると想定)。

テストビット:

if((flags & 0x08) == 0x08)
(ビット4が設定されている場合はtrue)トグルバック(1-0または0-1):
flags = flags ^ 0x08;
ビット4をゼロにリセットします。
flags = flags & 0xFFFFFF7F;


2
これは列挙型でも問題ないので-1ですか?プラス、値を手でコーディングすることは壊れやすいです...私は考え少なくとも書き込み~0x08の代わりに0xFFFFFFF7...(0x8のための実際のマスク)
ベン・モッシャー

1
最初はベンの-1は厳しいと思っていましたが、「0xFFFFFF7F」を使用すると、これは特に悪い例になります。
ToolmakerSteve 2014年

2

これは、Delphiでインデクサとしてセットを使用することから着想を得ました。

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}

2
BindingFlagsがバイト列挙型の場合、これはコンパイルされません。this.flags&=〜index;
2013年

0

C ++操作は次のとおりです。^〜(ビット演算ではなく、and、or、xorの場合)。また、ビットシフト演算である>>と<<も重要です。

したがって、フラグに設定されているビットをテストするには、次のようにします。if(flags&8)//テストビット4が設定されている


8
質問はC ++ではなくC#に関連しています
Andy Johnson

3
一方、C#は同じ演算子を使用します: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
@ workmad3を守るために、元のタグにはCおよびC ++が含まれていました
pqsk 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.