关于.net:枚举中最常见的C#按位运算

关于.net:枚举中最常见的C#按位运算

Most common C# bitwise operations on enums

为了我的一生,我不记得如何在位域中设置,删除,切换或测试位。 我不确定还是我将它们混在一起,因为我很少需要这些。 因此,拥有一个"位备忘单"将是不错的选择。

例如:

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

要么

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

您能否给出所有其他常见操作的示例,最好使用[Flags]枚举以C#语法显示?


我对这些扩展做了更多工作-您可以在此处找到代码

我写了一些扩展方法,这些方法扩展了我经常使用的System.Enum ...我并不是说它们是防弹的,但是它们已经帮助了...删除了评论...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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);
            }  
        }

    }
}

然后像下面这样使用

1
2
3
4
5
6
7
8
9
10
11
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

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

1
flags.HasFlag(FlagsEnum.Bit4)


习惯用法是使用按位或等号运算符设置位:

1
flags |= 0x04;

为了清楚一点,习惯用法是按位取反:

1
flags &= ~0x04;

有时您有一个偏移量来标识您的位,然后习惯用法是将这些与左移结合使用:

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

@德鲁

请注意,除了最简单的情况外,与手动编写代码相比,Enum.HasFlag会带来严重的性能损失。考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[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();
    }
}

HasFlags扩展方法经过1000万次迭代,耗时长达4793毫秒,而标准按位实现的耗时为27毫秒。


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

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

  • HasFlag不是类型安全的,因为它接受任何类型的枚举值参数,而不仅仅是给定的枚举类型。
  • HasFlag对于是否检查该值是否具有枚举值参数提供的所有标志或任何标志是模棱两可的。顺便说一句。
  • HasFlag相当慢,因为它需要装箱,这会导致分配并因此导致更多垃圾回收。
  • 由于.NET对标志枚举的支持有限,我编写了OSS库Enums.NET,该库解决了每个问题,并使处理标志枚举更加容易。

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

    组合标志

    .NET flags | otherFlags

    Enums.NET flags.CombineFlags(otherFlags)

    删除标志

    .NET flags & ~otherFlags

    Enums.NET flags.RemoveFlags(otherFlags)

    常见标志

    .NET flags & otherFlags

    Enums.NET flags.CommonFlags(otherFlags)

    切换标志

    .NET flags ^ otherFlags

    Enums.NET flags.ToggleFlags(otherFlags)

    有所有标志

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

    Enums.NET flags.HasAllFlags(otherFlags)

    有任何标志

    .NET (flags & otherFlags) != 0

    Enums.NET flags.HasAnyFlags(otherFlags)

    获取标志

    。净

    1
    2
    3
    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中。您可以在这里查看我的建议。


    C ++语法,假设第0位为LSB,并假设标志为无符号长整数:

    检查是否设置:

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

    检查是否未设置:

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

    组:

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

    明确:

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

    切换:

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


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

    测试位:

    1
    if((flags & 0x08) == 0x08)

    (如果设置了位4,则为true)
    向后切换(1-0或0-1-):

    1
    flags = flags ^ 0x08;

    将位4重置为零:

    1
    flags = flags & 0xFFFFFF7F;


    这是由于在Delphi中使用Sets作为索引器而产生的,这可以追溯到以下情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    /// 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];

      }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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;
            }
        }
    }

    C ++操作是:&| ^?(用于and,or,xor和非按位运算)。还感兴趣的是>>和<<,它们是位移操作。

    因此,要测试是否在标志中进行了设置,可以使用:
    if(flags&8)//测试第4位是否已设置


    推荐阅读