【问题标题】:How to Compare Flags in C#?如何比较 C# 中的标志?
【发布时间】:2008-09-02 18:28:16
【问题描述】:

我在下面有一个标志枚举。

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

我无法让 if 语句评估为真。

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

我怎样才能做到这一点?

【问题讨论】:

  • 如果我错了,请纠正我,0是否适合用作标志值?
  • @Roylee: 0 是可以接受的,最好设置一个“无”或“未定义”标志以测试没有设置标志。这绝不是必需的,但这是一个很好的做法。 Leonid 在他的回答中指出了重要的一点。
  • @Roylee Microsoft 实际上建议提供一个值为 0 的 None 标志。见msdn.microsoft.com/en-us/library/vstudio/…
  • 很多人还认为位比较太难读,所以应该避免使用标志集合,你可以只做collection.contains flag
  • 你非常接近,除了你必须反转你的逻辑,你需要按位& 运算符进行比较,| 就像一个加法:1|2=3,5|2=7,@987654329 @、7&2=28&2=00 计算结果为 false,其他所有结果为 true

标签: c# .net enums attributes flags


【解决方案1】:

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

【讨论】:

  • 啊,终于有开箱即用的东西了。这太好了,我等这个相对简单的功能已经很久了。很高兴他们决定把它塞进去。
  • 但是请注意,下面的答案显示了这种方法的性能问题——这对某些人来说可能是个问题。很高兴不适合我。
  • 此方法的性能考虑是装箱,因为它将参数作为Enum 类的实例。
  • 有关性能问题的信息,请查看此答案:stackoverflow.com/q/7368652/200443
【解决方案2】:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

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

FlagTest.Flag1 等效于带有 OP 枚举的 001。现在假设testItem 有Flag1 和Flag2(所以它是按位的101):

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

【讨论】:

  • 这里的逻辑到底是什么?为什么谓词一定要这样写?
  • @IanR.O'Brien Flag1 |标志 2 转换为与 011 相同的 001 或 010,现在如果您对 011 == Flag1 或转换的 011 == 001 进行相等,则始终返回 false。现在,如果你对 Flag1 进行按位与运算,那么它会转换为 011 AND 001,它返回 001 现在执行相等返回 true,因为 001 == 001
  • 这是最好的解决方案,因为 HasFlags 更加占用资源。而且HasFlags所做的一切,这里都是编译器完成的
【解决方案3】:

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

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

testItem(根据问题)定义为,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

那么,在if语句中,比较的左边是,

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

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

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

【讨论】:

    【解决方案4】:

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

    超过 1000 万次迭代,HasFlags 扩展方法花费了惊人的 4793 毫秒,而标准的按位实现则需要 27 毫秒。

    【讨论】:

    • 确实如此。如果您查看 HasFlag 的实现,您会发现它对两个操作数都执行“GetType()”,这非常慢。然后它执行“Enum.ToUInt64(value.GetValue());”在进行按位检查之前对两个操作数进行检查。
    • 我多次运行您的测试,HasFlags 得到了 ~500ms,按位得到了 ~32ms。虽然按位仍然快一个数量级,但 HasFlags 比您的测试低一个数量级。 (在 2.5GHz Core i3 和 .NET 4.5 上运行测试)
    • @MarioVW 在 .NET 4 上运行多次,i7-3770 在 AnyCPU(64 位)模式下提供约 2400 毫秒与约 20 毫秒,在 32 位模式下提供约 3000 毫秒与约 20 毫秒。 .NET 4.5 可能稍微优化了它。另请注意 64 位和 32 位构建之间的性能差异,这可能是由于更快的 64 位算术(参见第一条评论)。
    • («flag var» &amp; «flag value») != 0 对我不起作用。条件总是失败,我的编译器(Unity3D 的 Mono 2.6.5)在if (…) 中使用时报告“警告 CS0162:检测到无法访问的代码”
    • @wraith808:我意识到错误出在我的测试中,而你的测试是正确的——在使用[Flags] 时,枚举值上的 2 … = 1, … = 2, … = 4 的幂至关重要。我假设它将以1 开始条目并自动推进Po2s。在 MS .NET 和 Unity 过时的 Mono 中,行为看起来是一致的。我很抱歉把这个放在你身上。
    【解决方案5】:

    我设置了一个扩展方法来做到这一点:related question

    基本上:

    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
    

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

    【讨论】:

    • 这应该是一个评论,但由于我是一个新用户,看起来我还不能添加 cmets... public static bool IsSet( this Enum input, Enum matchTo ) { return ( Convert.ToUInt32( 输入 ) & Convert.ToUInt32( matchTo ) ) != 0;有没有办法兼容任何类型的枚举(因为如果你的枚举是 UInt64 类型或者可以有负值,它就不起作用)?
    • 这对于 Enum.HasFlag(Enum) 来说是非常多余的(在 .net 4.0 中可用)
    • @PPC 我不会说是多余的——很多人都在使用旧版本的框架进行开发。不过你是对的,.Net 4 用户应该使用 HasFlag 扩展名。
    • @Keith:还有一个显着的区别:((FlagTest)0x1).HasFlag(0x0) 将返回 true,这可能是也可能不是想要的行为
    【解决方案6】:

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

    [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 将始终为真,因为 ANYTHING & 0 始终 == 0。


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

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

    【讨论】:

    • 我刚刚修复了这样一个 0-flag 错误。我认为这是 .NET 框架(3.5)中的设计错误,因为您需要在测试之前知道哪个标志值是 0。
    【解决方案7】:
    if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
    {
    ...
    }
    

    【讨论】:

      【解决方案8】:

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

      这应该可以解决问题:

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

      编辑:修复了我的 if 检查 - 我重新回到了我的 C/C++ 方式(感谢 Ryan Farley 指出)

      【讨论】:

        【解决方案9】:

        关于编辑。你不能让它成真。我建议你将你想要的东西包装到另一个类(或扩展方法)中,以更接近你需要的语法。

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

        【讨论】:

          【解决方案10】:

          试试这个:

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

          【讨论】:

            【解决方案11】:

            即使没有 [Flags],你也可以使用类似的东西

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

            或者如果你有一个零值枚举

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

            【讨论】:

              猜你喜欢
              • 2012-04-05
              • 2022-01-10
              • 1970-01-01
              • 1970-01-01
              • 2016-12-26
              • 1970-01-01
              • 2011-08-12
              • 1970-01-01
              • 2021-02-10
              相关资源
              最近更新 更多