【问题标题】:Most common C# bitwise operations on enums枚举上最常见的 C# 位运算
【发布时间】:2010-09-10 18:15:02
【问题描述】:

在我的一生中,我不记得如何在位域中设置、删除、切换或测试位。要么我不确定,要么我把它们弄混了,因为我很少需要这些。所以有一个“小备忘单”会很好。

例如:

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

或

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

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

【问题讨论】:

  • 这在here之前已经回答了
  • 太糟糕了,链接没有出现在这个主题的问题提示中。
  • 该问题被标记为 c/c++,因此即使语法看起来相同,搜索有关 C# 信息的人也可能不会在那里查看。
  • 我不知道有一种不那么冗长的方式来进行位测试
  • @Andy,现在 .NET 4 中有一个用于位测试的 API。

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


【解决方案1】:

C++ 操作是: & | ^ ~ (for and, or, xor and not bitwise operations)。同样有趣的是>>和

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

【讨论】:

【解决方案2】:

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

flags |= 0x04;

要清除一点,成语是按位和取反:

flags &= ~0x04;

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

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

【讨论】:

    【解决方案3】:

    C++ 语法,假设第 0 位为 LSB,假设 flags 为 unsigned 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))
    

    【讨论】:

      【解决方案4】:

      要进行一些测试,您可以执行以下操作: (假设 flags 是 32 位数字)

      测试位:

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

      【讨论】:

      • -1 因为这甚至不打扰枚举?另外,手动编码值很脆弱...我至少会写 ~0x08 而不是 0xFFFFFFF7...(0x8 的实际掩码)
      • 起初我认为 Ben 的 -1 很苛刻,但使用“0xFFFFFF7F”确实使这成为一个特别糟糕的例子。
      【解决方案5】:

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

      我写了一些扩展我经常使用的 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
      

      【讨论】:

      • 我也发现这很有用 - 有什么想法可以修改它以使其适用于任何基础类型吗?
      • 这些扩展成就了我的一天、一周、一个月,甚至很可能是一年。
      • 谢谢!大家:请务必查看 Hugoware 链接到的更新。
      • 一组非常好的扩展。很遗憾他们需要拳击,尽管我想不出一个不使用拳击并且简洁的替代方案。即使是 Enum 上的新 HasFlag 方法也需要装箱。
      • @Drew:请参阅code.google.com/p/unconstrained-melody,了解避免拳击的方法:)
      【解决方案6】:

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

      flags.HasFlag(FlagsEnum.Bit4)
      

      【讨论】:

      • +1 指出这一点,尽管FlagsEnum 是一个丑陋的名字。 :)
      • @Jim,也许吧。这只是原始问题中使用的示例名称,因此您可以在代码中随意更改它。
      • 我知道!但丑陋的名字就像 IE6,可能永远不会消失 :(
      • @JimSchubert,再次,我只是从原始问题中复制了类型名称,以免混淆问题。 .NET Enumeration Type Naming Guidelines 表示所有[Flags] 枚举都应该有复数名称,因此名称FlagsEnum 比丑陋更严重。
      • 我也推荐Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries。购买有点贵,但我相信 Safari Online 和 Books24x7 都为订阅者提供。
      【解决方案7】:

      @德鲁

      请注意,除了最简单的情况外,与手动编写代码相比,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 毫秒,而标准的按位实现则需要 27 毫秒。

      【讨论】:

      • 虽然确实很有趣并且值得指出。您确实需要考虑使用情况。根据这一点,如果您不执行几十万或更多的操作,您可能甚至都不会注意到这一点。
      • HasFlag 方法涉及装箱/拆箱,这是造成这种差异的原因。但是成本是如此微不足道(0.4μs),除非你处于一个紧密的循环中,否则我会在任何一天都采用更易读(并且不太可能出现错误)的声明性 API 调用。
      • 根据使用情况,这可能是个问题。由于我经常使用装载机,因此我认为指出这一点很好。
      【解决方案8】:

      这是在 Delphi 中使用 Set 作为索引器的启发,早在:

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

      【讨论】:

      • 如果 BindingFlags 是字节枚举,这甚至不会编译:this.flags &= ~index;
      【解决方案9】:

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

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

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

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

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

      组合标志

      .NET             flags | otherFlags

      枚举.NET flags.CombineFlags(otherFlags)


      删除标志

      .NET             flags &amp; ~otherFlags

      枚举.NET flags.RemoveFlags(otherFlags)


      常用标志

      .NET             flags &amp; otherFlags

      枚举.NET flags.CommonFlags(otherFlags)


      切换标志

      .NET             flags ^ otherFlags

      枚举.NET flags.ToggleFlags(otherFlags)


      具有所有标志

      .NET             (flags &amp; otherFlags) == otherFlags 或 flags.HasFlag(otherFlags)

      枚举.NET flags.HasAllFlags(otherFlags)


      有任何标志

      .NET             (flags &amp; otherFlags) != 0

      枚举.NET flags.HasAnyFlags(otherFlags)


      获取标志

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


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

      【讨论】:

        【解决方案10】:

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

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

        【讨论】:

        • 我喜欢这种简洁明了的方法,但是如果 Add 和 Remove 方法会改变原来的方法不是更好/更清晰吗?
        • 由于 enum 是一种值类型,如果没有 ref 参数,这是不可能的,它不能作为扩展方法。
        【解决方案11】:

        按位 (Flags) 枚举指南

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

        Operation Syntax Example
        On |= e |= E.A
        Off &amp;= + ~ e &amp;= ~E.A
        Toggle ^= e ^= E.A
        Test (.NET API) .HasFlag e.HasFlag(E.A)
        Test (bitwise) (see example) (e &amp; 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,
        

        【讨论】:

          猜你喜欢
          • 2022-03-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-28
          相关资源
          最近更新 更多