关于.net:如何在C#中比较标志?

关于.net:如何在C#中比较标志?

How to Compare Flags in C#?

我下面有一个标志枚举。

1
2
3
4
5
6
7
8
[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

我无法使if语句评估为true。

1
2
3
4
5
6
7
FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

if (testItem == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

我如何才能做到这一点?


在.NET 4中,有一个新方法Enum.HasFlag。这使您可以编写:

1
2
3
4
if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

IMO更具可读性。

.NET源表明这与接受的答案具有相同的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Boolean HasFlag(Enum flag) {
    if (!this.GetType().IsEquivalentTo(flag.GetType())) {
        throw new ArgumentException(
            Environment.GetResourceString(
               "Argument_EnumTypeDoesNotMatch",
                flag.GetType(),
                this.GetType()));
    }

    ulong uFlag = ToUInt64(flag.GetValue());
    ulong uThis = ToUInt64(GetValue());
    // test predicate
    return ((uThis & uFlag) == uFlag);
}

1
2
3
4
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1)是按位与运算。

FlagTest.Flag1等同于带有OP枚举的001。现在,假设testItem具有Flag1和Flag2(因此按位101):

1
2
3
4
  001
 &101
 ----
  001 == FlagTest.Flag1


对于那些无法想象被接受的解决方案正在发生什么的人
(这是),

1
2
3
4
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem(根据问题)定义为

1
2
3
4
testItem
 = flag1 | flag2  
 = 001 | 010  
 = 011

然后,在if语句中,比较的左侧是

1
2
3
(testItem & flag1)
 = (011 & 001)
 = 001

以及完整的if语句(如果在testItem中设置了flag1,则结果为true),

1
2
3
(testItem & flag1) == flag1
 = (001) == 001
 = true

@ phil-devaney

请注意,除了最简单的情况外,与手动编写代码相比,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毫秒。


我设置了一个扩展方法来做到这一点:相关问题。

基本上:

1
2
3
4
public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

然后,您可以执行以下操作:

1
2
3
4
FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

if( testItem.IsSet ( FlagTests.Flag1 ) )
    //Flag1 is set

顺便说一下,我用于枚举的约定对于标准是单数,对于标志是复数。这样,您就可以从枚举名称中知道它是否可以容纳多个值。


另一条建议...永远不要对值为" 0"的标志进行标准的二进制检查。您对此标志的检查将始终为true。

1
2
3
4
5
6
7
8
[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value ="FullInfo")]
    FullInfo=0,
    [EnumMember(Value ="BusinessData")]
    BusinessData=1
}

如果对FullInfo二进制检查输入参数-您将得到:

1
2
detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;

bPRez将始终为true,并且ANYTHING&0 always == 0。

相反,您应该只检查输入值是否为0:

1
bool bPRez = (detailLevel == LevelOfDetail.FullInfo);


1
2
3
4
if((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
...
}

关于编辑。您无法实现。我建议您将所需的内容包装到另一个类(或扩展方法)中,以更接近所需的语法。

1
2
3
4
5
6
7
public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}

对于位运算,您需要使用按位运算符。

这应该可以解决问题:

1
2
3
4
5
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

编辑:修复了我的if支票-我重新使用了C / C ++的方式(感谢Ryan Farley指出了这一点)


尝试这个:

1
2
3
4
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}

基本上,您的代码在询问是否设置两个标志都与设置一个标志相同,这显然是错误的。如果上面的代码被置位,则上面的代码将仅保留Flag1的位置,然后将该结果与Flag1进行比较。


即使没有[Flags],您也可以使用这样的内容

1
2
3
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

或者您有一个零值枚举

1
2
3
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}


推荐阅读