如何在C#中比较标志?


155

我下面有一个标志枚举。

[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

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

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

我如何才能做到这一点?


如果我错了,请纠正我,0是否适合用作标志值?
罗伊·李

4
@Roylee:0是可以接受的,最好具有“ None”或“ Undefined”标志,以测试未设置标志。绝不是必需的,但这是一个好习惯。列昂尼德(Leonid)在回答中指出了要记住的重要事情。
安迪

5
@Roylee实际上,Microsoft实际上建议提供None一个值为零的标志。见msdn.microsoft.com/en-us/library/vstudio/...
ThatMatthew

许多人还认为,位比较太难读了,因此应避免使用标志集合,在这里您可以进行collection.contains标志
MikeT

你是相当接近,只是你需要反转你的逻辑,你需要按位&经营者进行比较,|就像是一个加法:1|2=35|2=73&2=27&2=28&2=00评估为false,其他均为true
Damian Vogel '18

Answers:


321

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

if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

IMO更具可读性。

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

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

23
啊,终于有东西了。太好了,我已经等了很长一段时间了。很高兴他们决定滑进去。
罗布面包车Groenewoud

9
但是请注意,下面的答案显示了此方法的性能问题-这对于某些人来说可能是个问题。很高兴不适合我。
安迪·莫蒂默

2
此方法的性能考虑因素是装箱,因为它将参数作为Enum类的实例。
Adam Houldsworth

1
有关性能问题的信息,请查看以下答案:stackoverflow.com/q/7368652/200443
Maxence

180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

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

FlagTest.Flag1相当于001OP的枚举。现在,假设testItem有Flag1和Flag2(按位101):

  001
 &101
 ----
  001 == FlagTest.Flag1

2
这里的逻辑到底是什么?为什么谓词必须这样写?
伊恩·奥布莱恩

4
@ IanR.O'Brien Flag1 | 标志2转换为001或010,与011相同,现在,如果您等于011 == Flag1或转换的011 == 001,则始终返回false。现在,如果对Flag1进行按位与运算,则它将转换为011和001,并返回001,现在相等返回true,因为001 == 001
pqsk 2015年

这是最好的解决方案,因为HasFlags占用的资源更多。而且,HasFlags所做的一切,都是由编译器完成的
Sebastian

78

对于那些无法想象接受的解决方案正在发生的事情(就是这样)的人,

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (根据问题)定义为

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

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

(testItem & flag1) 
 = (011 & 001) 
 = 001

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

(testItem & flag1) == flag1
 = (001) == 001
 = true

25

@ phil-devaney

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


5
确实。如果查看HasFlag的实现,您会发现它对两个操作数都执行“ GetType()”,这非常慢。然后执行“ Enum.ToUInt64(value.GetValue());” 在进行按位检查之前,先对两个操作数进行操作。
user276648 2011年

1
我对您的测试进行了几次测试,HasFlags的测试时间约为500毫秒,按位测试的速度约为32毫秒。HasFlags虽然按位速度仍然快一个数量级,但是比您的测试低一个数量级。(在2.5GHz Core i3和.NET 4.5上进行测试)
MarioVW 2013年

1
@MarioVW在.NET 4上运行了好几次,i7-3770给出了〜2400ms,而在AnyCPU(64位)模式下为〜20ms,而给出了3000ms与32位模式下为〜20ms。.NET 4.5可能对其进行了一些优化。还要注意64位和32位版本之间的性能差异,这可能是由于64位算法更快(请参阅第一个注释)。
Bob

1
(«flag var» & «flag value») != 0对我不起作用。该条件始终失败,并且我的编译器(Unity3D的Mono 2.6.5)在中使用时报告“警告CS0162:检测到无法访问的代码”if (…)
Slipp D. Thompson

1
@ wraith808:我意识到我的测试中犯了一个错误,即您的测试正确无误- … = 1, … = 2, … = 4使用时,枚举值的2的幂非常重要[Flags]。我以为它将在1Po2s 处开始条目并自动前进。在MS .NET和Unity的过时的Mono中,行为看起来是一致的。抱歉,这件事对您不利。
斯利普·汤普森

21

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

基本上:

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

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

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

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

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


这应该是评论,但由于我是新用户,所以我似乎还不能添加评论... public static bool IsSet(此Enum输入,Enum matchTo){return(Convert.ToUInt32(input)&Convert .ToUInt32(matchTo))!= 0; }有没有一种方法可以与任何类型的枚举兼容(因为如果您的枚举类型为UInt64或可以具有负值,那么它将无法正常工作)?
user276648 '02

这与Enum.HasFlag(Enum)(在.net 4.0中可用)相当冗余
PPC

1
@PPC我不会确切地说是多余的-许多人正在使用较旧版本的框架进行开发。没错,.Net 4用户应该改用HasFlag扩展名。
Keith 2012年

4
@Keith:此外,还有一个显着的区别:(((FlagTest)0x1).HasFlag(0x0)将返回true,这可能是也可能不是想要的行为
PPC

19

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

[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value = "FullInfo")]
    FullInfo=0,
    [EnumMember(Value = "BusinessData")]
    BusinessData=1
}

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

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

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


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

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

我只是修复了这样一个0标志错误。我认为这是.NET Framework(3.5)中的设计错误,因为在测试它之前,您需要知道哪个标志值是0。
thersch


5

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

这应该可以解决问题:

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

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


5

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

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}

4

试试这个:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
基本上,您的代码会询问设置两个标志是否与设置一个标志相同,这显然是错误的。如果上面的代码被置位,则上面的代码将仅保留Flag1的位置,然后将该结果与Flag1进行比较。


1

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

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

或者您有一个零值枚举

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
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.