212

For the life of me, I can't remember how to set, delete, toggle or test a bit in a bitfield. Either I'm unsure or I mix them up because I rarely need these. So a "bit-cheat-sheet" would be nice to have.

For example:

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

or

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

Can you give examples of all the other common operations, preferably in C# syntax using a [Flags] enum?

4

11 回答 11

296

我对这些扩展做了更多的工作——你可以在这里找到代码

我写了一些扩展我经常使用的 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
于 2009-01-06T16:31:05.010 回答
114

在 .NET 4 中,您现在可以编写:

flags.HasFlag(FlagsEnum.Bit4)
于 2011-05-30T17:48:11.910 回答
97

The idiom is to use the bitwise or-equal operator to set bits:

flags |= 0x04;

To clear a bit, the idiom is to use bitwise and with negation:

flags &= ~0x04;

Sometimes you have an offset that identifies your bit, and then the idiom is to use these combined with left-shift:

flags |= 1 << offset;
flags &= ~(1 << offset);
于 2008-09-18T15:50:16.257 回答
22

@Drew

Note that except in the simplest of cases, the Enum.HasFlag carries a heavy performance penalty in comparison to writing out the code manually. Consider the following code:

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

Over 10 million iterations, the HasFlags extension method takes a whopping 4793 ms, compared to the 27 ms for the standard bitwise implementation.

于 2011-08-24T19:51:30.633 回答
16

不幸的是,.NET 的内置标志枚举操作非常有限。大多数时候,用户都需要弄清楚按位运算逻辑。

在 .NET 4 中,HasFlag添加了Enum有助于简化用户代码的方法,但不幸的是它存在许多问题。

  1. HasFlag不是类型安全的,因为它接受任何类型的枚举值参数,而不仅仅是给定的枚举类型。
  2. HasFlag关于它是否检查值是否具有枚举值参数提供的所有或任何标志是模棱两可的。顺便说一句。
  3. HasFlag相当慢,因为它需要装箱,这会导致分配,从而导致更多的垃圾收集。

部分由于 .NET 对标志枚举的有限支持,我编写了 OSS 库Enums.NET,它解决了这些问题,并使处理标志枚举变得更加容易。

下面是它提供的一些操作以及仅使用 .NET 框架的等效实现。

组合标志

。网             flags | otherFlags

枚举.NET flags.CombineFlags(otherFlags)


删除标志

。网             flags & ~otherFlags

枚举.NET flags.RemoveFlags(otherFlags)


通用标志

。网             flags & otherFlags

枚举.NET flags.CommonFlags(otherFlags)


切换标志

。网             flags ^ otherFlags

枚举.NET flags.ToggleFlags(otherFlags)


拥有所有标志

.NET             (flags & otherFlags) == otherFlagsflags.HasFlag(otherFlags)

枚举.NET flags.HasAllFlags(otherFlags)


有任何标志

。网             (flags & otherFlags) != 0

枚举.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))`

枚举.NET flags.GetFlags()


我正在尝试将这些改进整合到 .NET Core 中,甚至可能最终整合到完整的 .NET Framework 中。你可以在这里查看我的建议。

于 2017-06-14T17:27:58.080 回答
7

C++ 语法,假设位 0 是 LSB,假设标志是无符号长的:

检查是否设置:

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))
于 2008-09-18T15:57:25.867 回答
5

为了获得最佳性能和零垃圾,请使用以下命令:

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;
        }
    }
}
于 2019-03-03T20:19:52.867 回答
2

这是受到在 Delphi 中使用 Sets 作为索引器的启发,早在以下情况下:

/// 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];

  }
}
于 2012-03-09T22:09:36.517 回答
2

要测试一下,您将执行以下操作:(假设标志是 32 位数字)

测试位:

if((flags & 0x08) == 0x08)
(如果设置了第 4 位,则为真)切换回(1 - 0 或 0 - 1):
flags = flags ^ 0x08;
将位 4 重置为零:
flags = flags & 0xFFFFFF7F;

于 2008-09-18T16:00:01.803 回答
2

按位 ( Flags) 枚举指南

旧的,但想试一试备忘单,即使是为了我自己的参考:

手术 句法 例子
|= e |= E.A
离开 &=+~ e &= ~E.A
切换 ^= e ^= E.A
测试(.NET API) .HasFlag e.HasFlag(E.A)
测试(按位) (见例子) (e & E.A) == E.A

例子

[Flags]
enum E {
    A = 0b1,
    B = 0b10,
    C = 0b100
}

E e = E.A;        // Assign (e = A)
e |= E.B | E.C;   // Add    (e = A, B, C)
e &= ~E.A & ~E.B; // Remove (e = C) -- alt syntax: &= ~(E.A | E.B)
e ^= E.A | E.C;   // Toggle (e = A)
e.HasFlag(E.A);   // Test   (returns true)

// Testing multiple flags using bit operations:
bool hasAandB = ( e & (E.A | E.B) ) == (E.A | E.B);

奖励:定义一个Flags枚举

通常,我们使用这样的整数:

[Flags]
enum E {
    A = 1,
    B = 2,
    C = 4,
    // etc.

但是当我们接近更大的数字时,计算下一个值并不容易:

  // ...
  W = 4194304,
  X = 8388608,
  // ..

但是,有两种选择:二进制和十六进制文字。

对于Binary,只需0在前一个值的末尾附加 a :

[Flags]
enum E {
    A = 0b1,
    B = 0b10,
    C = 0b100,
    // ...
    W = 0b100_0000_0000_0000_0000_0000,
    X = 0b1000_0000_0000_0000_0000_0000,

Hexidecimal也有一个方便的模式,可能看起来不那么难看:循环 1、2、4、8,在每次完整迭代后添加一个零。

[Flags]
enum E {
    A = 0x1,
    B = 0x2,
    C = 0x4,
    D = 0x8,
    E = 0x10, // 16
    E = 0x20, // 32, etc.
    // ...
    W = 0x400000,
    X = 0x800000,
于 2021-07-15T18:26:14.330 回答
0

C++ operations are: & | ^ ~ (for and, or, xor and not bitwise operations). Also of interest are >> and <<, which are bitshift operations.

So, to test for a bit being set in a flag, you would use: if (flags & 8) //tests bit 4 has been set

于 2008-09-18T15:50:04.230 回答