枚举上最常见的C#按位运算


201

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

例如:

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

要么

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

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


5
这已经在这里
Greg Rogers

7
太糟糕了,该链接没有出现在此主题的问题提示中。
cori

10
不过,该问题是为c / c ++进行标记的,因此即使语法似乎相同,但是搜索C#信息的人也可能不会在那看到。
亚当·拉瑟克

我不知道采用哪种更冗长的方式进行位测试
安迪·约翰逊

2
@ Andy,.NET 4中现在有一个用于位测试的API。
德鲁·诺阿克斯

Answers:


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,2010年

谢谢!所有人:请务必查看Hugoware链接到的更新。
Helge Klein'Mar

一组非常不错的扩展。他们需要装箱很可惜,尽管我想不出一个不使用装箱而且简洁的替代品。甚至新HasFlag方法也Enum需要装箱。
德鲁·诺阿克斯

4
@Drew:有关避免拳击的方法,请参见code.google.com/p/unconstrained-melody:)
Jon Skeet

109

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

flags.HasFlag(FlagsEnum.Bit4)

4
+1指出这一点,尽管这FlagsEnum是一个丑陋的名字。:)
吉姆·舒伯特

2
@吉姆,也许。正如原始问题中所使用的那样,它只是一个示例名称,因此您可以在代码中自由更改它。
德鲁·诺阿克斯

14
我知道!但是丑陋的名字就像IE6一样,可能永远不会消失:(
吉姆·舒伯特

5
@JimSchubert,我还是再次复制了原始问题的类型名称,以免混淆问题。在.NET枚举类型命名原则表明,所有[Flags]枚举应该有pluralised的名字,所以这个名字FlagsEnum有甚至比丑更严重的问题。
Drew Noakes

1
我还建议框架设计指南:可重用.NET库的约定,惯用语和模式。买起来有点贵,但是我相信Safari Online和Books24x7都为订阅者提供。
Jim Schubert

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

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


10
虽然当然很有趣,但值得指出。您确实需要考虑用法。据此,如果您不执行数十万次或更多次操作,您甚至可能根本不会注意到这一点。
约书亚·海斯

7
HasFlag方法涉及装箱/拆箱,这就是造成这种差异的原因。但是成本是如此微不足道(0.4µs),以至于除非您处于一个严密的循环中,否则我每天都会接受更具可读性(且不太可能出错的)声明式API调用。
德鲁·诺阿克斯

8
根据使用情况,可能是个问题。而且由于我与装载机打交道了很多,所以我指出要指出是一件好事。
Chuck Dee 2012年

11

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

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

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

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

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

组合标志

。净             flags | otherFlags

枚举 flags.CombineFlags(otherFlags)


删除标志

。净             flags & ~otherFlags

枚举 flags.RemoveFlags(otherFlags)


常见标志

。净             flags & otherFlags

枚举 flags.CommonFlags(otherFlags)


切换标志

。净             flags ^ otherFlags

枚举 flags.ToggleFlags(otherFlags)


有所有标志

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

枚举 flags.HasAllFlags(otherFlags)


有任何标志

。净             (flags & otherFlags) != 0

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

枚举 flags.GetFlags()


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


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

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的实际掩码)
Ben Mosher 2012年

1
起初我以为Ben的-1很苛刻,但是使用“ 0xFFFFFF7F”确实使这成为一个特别糟糕的例子。
制造商史蒂夫(Steve)2014年

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

  }
}

2
如果BindingFlags是字节枚举,则甚至不会编译:this.flags&=〜index;
2013年

0

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

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


8
问题与c#而非c ++有关
安迪·约翰逊

3
另一方面,C#使用相同的运算符:msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
为了捍卫@ workmad3,原始标签使用了C和C ++
pqsk 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.