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

ste*_*enj 197 .net c# enums flags bit-manipulation

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

例如:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.
Run Code Online (Sandbox Code Playgroud)

要么

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?
Run Code Online (Sandbox Code Playgroud)

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

Hug*_*are 285

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

我写了一些扩展方法,扩展了我经常使用的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);
            }  
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

然后像下面一样使用它们

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
Run Code Online (Sandbox Code Playgroud)

  • 这些扩展只是我的一天,我的一周,我的月份,很可能是我的一年. (6认同)
  • @Drew:请参阅http://code.google.com/p/unconstrained-melody以获取避免装箱的方法:) (4认同)

Dre*_*kes 107

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

flags.HasFlag(FlagsEnum.Bit4)
Run Code Online (Sandbox Code Playgroud)

  • 我知道!但丑陋的名字就像IE6,可能永远不会消失:( (13认同)
  • @JimSchubert,我再次从原始问题中复制了类型名称,以免混淆问题.[.NET枚举类型命名指南](http://msdn.microsoft.com/en-us/library/4x252001(v = vs.71).aspx)表示所有`[Flags]`枚举应该有多个名称所以"FlagsEnum"这个名字比丑陋更严重. (5认同)
  • 虽然`FlagsEnum`是一个丑陋的名字,但是用于指出它的+1.:) (4认同)
  • @Jim,也许吧。它只是原始问题中使用的示例名称,因此您可以在代码中自由更改它。 (2认同)

Ste*_*ken 88

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

flags |= 0x04;
Run Code Online (Sandbox Code Playgroud)

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

flags &= ~0x04;
Run Code Online (Sandbox Code Playgroud)

有时你有一个标识你的位的偏移量,然后成语是使用这些与左移相结合:

flags |= 1 << offset;
flags &= ~(1 << offset);
Run Code Online (Sandbox Code Playgroud)


Chu*_*Dee 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

超过1000万次迭代,HasFlags扩展方法需要高达4793 ms,而标准按位实现则为27 ms.

  • 虽然肯定有趣并且很好​​地指出.您需要考虑使用情况.根据这个,如果你没有表演几十万或更多的操作你可能甚至没有注意到这一点. (10认同)
  • 根据使用情况,可能会出现问题.由于我与装载机工作了很多,我觉得很好指出. (8认同)
  • 'HasFlag`方法涉及装箱/拆箱,这解释了这种差异.但是成本是如此微不足道(0.4μs),除非你处于紧张的循环中,否则我会在任何一天采用更具可读性(并且不太可能错误)的声明性API调用. (7认同)

Tyl*_*ley 9

不幸的是,.NET的内置标志枚举操作非常有限.大多数时候,用户只能计算出按位运算逻辑.

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

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

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

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

结合旗帜

.净             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


删除标志

.净             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


常见的旗帜

.净             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


切换标志

.净             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


拥有所有旗帜

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

Enums.NET flags.HasAllFlags(otherFlags)


有任何旗帜

.净             (flags & otherFlags) != 0

Enums.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))`
Run Code Online (Sandbox Code Playgroud)

Enums.NET flags.GetFlags()


我正在尝试将这些改进纳入.NET Core,最终可能是完整的.NET Framework.你可以在这里查看我的建议.


Pet*_*esh 7

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

检查是否设置:

flags & (1UL << (bit to test# - 1))
Run Code Online (Sandbox Code Playgroud)

检查是否未设置:

invert test !(flag & (...))
Run Code Online (Sandbox Code Playgroud)

组:

flag |= (1UL << (bit to set# - 1))
Run Code Online (Sandbox Code Playgroud)

明确:

flag &= ~(1UL << (bit to clear# - 1))
Run Code Online (Sandbox Code Playgroud)

切换:

flag ^= (1UL << (bit to set# - 1))
Run Code Online (Sandbox Code Playgroud)


小智 6

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

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Con*_*Low 5

按位 ( 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);
Run Code Online (Sandbox Code Playgroud)

奖励:定义一个Flags枚举

通常,我们使用整数,如下所示:

[Flags]
enum E {
    A = 1,
    B = 2,
    C = 4,
    // etc.
Run Code Online (Sandbox Code Playgroud)

但当我们接近更大的数字时,计算下一个值就不那么容易了:

  // ...
  W = 4194304,
  X = 8388608,
  // ..
Run Code Online (Sandbox Code Playgroud)

然而,还有一些替代方案:二进制和十六进制文字。

对于Binary0 ,只需在前一个值的末尾添加一个:

[Flags]
enum E {
    A = 0b1,
    B = 0b10,
    C = 0b100,
    // ...
    W = 0b100_0000_0000_0000_0000_0000,
    X = 0b1000_0000_0000_0000_0000_0000,
Run Code Online (Sandbox Code Playgroud)

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

[Flags]
enum E {
    A = 0x1,
    B = 0x2,
    C = 0x4,
    D = 0x8,
    E = 0x10, // 16
    F = 0x20, // 32, etc.
    // ...
    W = 0x400000,
    X = 0x800000,
Run Code Online (Sandbox Code Playgroud)


wor*_*ad3 1

C++ 操作有: & | ^ ~(用于与、或、异或和非按位运算)。同样令人感兴趣的是 >> 和 <<,它们是位移操作。

因此,要测试标志中设置的位,您可以使用: if (flags & 8) //tests bit 4 has been set

  • 问题涉及 c#,而不是 c++ (8认同)
  • 另一方面,C# 使用相同的运算符:http://msdn.microsoft.com/en-us/library/6a71f45d.aspx (4认同)
  • 为了捍卫@workmad3,原始标签有 C 和 C++ (4认同)