枚举上最常见的C#按位操作

时间:2008-09-18 15:46:20

标签: c# .net enums bit-manipulation flags

对于我的生活,我不记得如何设置,删除,切换或测试位域中的位。要么我不确定,要么混淆它们,因为我很少需要这些。因此,“比特作弊表”会很好。

例如:

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

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

您能举例说明所有其他常见操作,最好是使用[Flags]枚举的C#语法吗?

11 个答案:

答案 0 :(得分:284)

我对这些扩展做了更多工作 - You can find the code here

我写了一些扩展方法,扩展了我经常使用的System.Enum ...我并没有声称它们是防弹的,但是它们已经帮助了...... 注释被删除了...... < / p>

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 :(得分:106)

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

flags.HasFlag(FlagsEnum.Bit4)

答案 2 :(得分:86)

成语是使用按位或等于运算符来设置位:

flags |= 0x04;

要清除一点,成语是按位使用和否定:

flags &= ~0x04;

有时你会有一个标识你位的偏移量,然后成语就是将它们与左移结合使用:

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

答案 3 :(得分:22)

@Drew

请注意,除了最简单的情况外,与手动编写代码相比,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扩展方法需要高达4793 ms,而标准按位实现则为27 ms。

答案 4 :(得分:9)

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

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

  1. HasFlag不是类型安全的,因为它接受任何类型的枚举值参数,而不仅仅是给定的枚举类型。
  2. HasFlag关于它是否具有枚举值参数提供的全部或任何标志是不明确的。顺便说一下。
  3. flags | otherFlags相当慢,因为它需要装箱,这会导致分配,从而导致更多的垃圾收集。
  4. 部分由于.NET对标志枚举的支持有限,我编写了OSS库Enums.NET,它解决了这些问题并使得处理标志枚举变得更加容易。

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

    组合标志

    .NET flags.CombineFlags(otherFlags)

    Enums.NET flags & ~otherFlags

    删除标志

    .NET flags.RemoveFlags(otherFlags)

    Enums.NET flags & otherFlags

    Common Flags

    .NET flags.CommonFlags(otherFlags)

    Enums.NET flags ^ otherFlags

    切换标志

    .NET flags.ToggleFlags(otherFlags)

    Enums.NET (flags & otherFlags) == otherFlags

    拥有所有标志

    .NET flags.HasFlag(otherFlags)flags.HasAllFlags(otherFlags)

    Enums.NET (flags & otherFlags) != 0

    有任何标志

    .NET flags.HasAnyFlags(otherFlags)

    Enums.NET 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()

    Enums.NET #include "stdafx.h" #include <iostream> #include "Calculator.h" using namespace std; float Calculator::add() { cout << "Enter 1 number: "; cin >> a; cout << "Enter 2 number: "; cin >> b; system("cls"); cout << a + b << endl; return 0; } float Calculator::sub() { cout << "Enter 1 number: "; cin >> a; cout << "Enter 2 number: "; cin >> b; system("cls"); cout << a - b << endl; return 0; } float Calculator::mul() { cout << "Enter 1 number: "; cin >> a; cout << "Enter 2 number: "; cin >> b; system("cls"); cout << a*b << endl; return 0; } float Calculator::div() { cout << "Enter 1 number: "; cin >> a; cout << "Enter 2 number: "; cin >> b; system("cls"); cout << a / b << endl; return 0; } int main() { int choose; Calculator k1; cout << "1.Add\n"; cout << "2.Sub\n"; cout << "3.Mul\n"; cout << "4.Div\n"; cout << "Choose: "; cin >> choose; if (choose == '1') k1.add(); else if (choose == '2') k1.sub(); else if (choose == '3') k1.mul(); else if (choose == '4') k1.div(); system("pause"); return 0; }

    我正在尝试将这些改进整合到.NET Core中,最终可能是完整的.NET Framework。您可以查看我的提案here

答案 5 :(得分: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))

答案 6 :(得分:3)

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

测试位:  

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

答案 7 :(得分:2)

这是受到使用Set作为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];

  }
}

答案 8 :(得分:0)

C ++操作是:&amp; | ^〜(for和,或者,xor而不是按位运算)。还感兴趣的是&gt;&gt;和&lt;&lt;,这是比特操作。

因此,要测试在标志中设置的位,您将使用:     if(flags&amp; 8)//测试位4已设置

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

按位 (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,
  // ..

不过,有几种替代方法:二进制和十六进制文字。

对于二进制,只需在前一个值的末尾附加一个 0

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

十六进制 也有一个方便的模式,可能看起来不那么难看:循环 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,