按位运算 - 检查和移除

5
请注意下面的简单示例:
Module Module1
    <Flags>
    Public Enum Names
        None    = 0
        Test    = 1
        Test2   = 2
        Test3   = 4
        Test4   = 8
    End Enum

    Sub Main()
        Dim test As Names = Names.Test Or Names.Test3
        If (test And Names.Test3) = Names.Test3
            Console.WriteLine("TRUE")
        Else
            Console.WriteLine("FALSE")
        End If
    End Sub
End Module

我的问题的第一部分涉及到这行代码:If (test And Names.Test3) = Names.Test3
如果仅检查标志是否存在,检查If test And Names.Test3是否更好呢? 如果它评估为非零值(表示标志存在),则条件的结果将是True
是否有使用第一种方式而不是第二种进行检查的充分理由? (虽然我的答案适用于VB.NET,但我也想知道这是否是其他地方可能出现的潜在陷阱,例如C#,C ++等)。
另外,在删除标志方面,似乎有两种方法: test = test Xor Names.Test3test = test And Not Names.Test3 然而,第一种方法会添加标志(如果标志丢失)并删除它(如果存在),而第二种方法只会删除它。那是唯一的区别吗? 还是有其他原因我应该更喜欢其中一种方法?

通常我们只喜欢一个问题。但对于你的第二个问题,这是你的情况的区别所在。当你开始组合标志时(test Xor(Names.Test2 Or Names.Test3)),情况会变得更加复杂。在这种情况下,如果你没有同时拥有两个标志,Xor可能会产生奇怪的结果。 - the_lotus
我认为我的问题很小,不需要分开讨论。你标记的 Xor 看起来对我来说工作得很好,它只是一次性完成所有操作。在你的例子中,它添加了 Names.Test2,因为它不存在,并删除了 Names.Test3,因为它已经存在! - Interminable
2个回答

5
您说得对,您可以有效地用以下代码替换原有代码: If (test And Names.Test3) Then 但是,如果使用Option Strict On编译时会出现错误:Option Strict On不允许从'Names'到'Boolean'的隐式转换,所以要使其能够编译,需要在它周围加上一个CBool
因此,总的来说,我认为最好使用第一种方法,因为意图非常清晰:您正在检查是否设置了一个位。
至于标记删除,即取消设置位,您应该使用以下代码: test = test And Not Names.Test3 使用Xor的效果是切换值。
以下内容可能会有所帮助(特别是如果将它们制作成扩展方法):
Public Function SetBit(ByVal aValue As Names, ByVal aBit As Names) As Names
    Return (aValue Or aBit)
End Function

Public Function ClearBit(ByVal aValue As Names, ByVal aBit As Names) As Names
    Return (aValue And Not aBit)
End Function

Public Function IsBitSet(ByVal aValue As Names, ByVal aBit As Names) As Boolean
    Return ((aValue And aBit) = aBit)
End Function

Public Function ToggleBit(ByVal aValue As Names, ByVal aBit As Names) As Names
    Return (aValue Xor aBit)
End Function

啊!我通常将Option StrictOption Explicit都设置为On,但是我刚刚创建这个简单的测试项目来理解位运算……然后忘了!感谢指出这一点。这绝对是选择第一种方法的好理由。您对我关于标志移除的第二部分问题有什么想法吗? - Interminable
2
你可以将Strict默认设置为On:https://dev59.com/questions/fFTTa4cB1Zd3GeqPvs25 - 这应该是开箱即用的设置。在这里投票支持微软进行更改:https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/10672947-set-option-strict-on-by-default-instead-of-off - Matt Wilko
1
我知道可以在项目设置中将其设置为整个项目,但我不知道我可以让Visual Studio默认执行此操作!非常方便。您提供的想法得到了我的支持。 - Interminable
等一下,你是说test = test And Not Names.Test3在使用Option Strict时不能工作吗?还是你指的是这部分代码:If test And Names.Test3 - Visual Vincent
1
哦,等等,我看错了你的回答。哈哈。谢谢你--我喜欢你的回答,但是由于我今天已经用了40个投票,所以明天才能点赞。 :) - Visual Vincent
显示剩余2条评论

2

请记住,Flags枚举不一定都是纯粹的单个位值。例如,想象一下(用更好的名称)你的枚举是:

<Flags>
Public Enum Names
    None    = 0
    Test    = 1
    Test2   = 2
    Test3   = 4
    Test4   = 8
    Test2AndTest4 = 10
End Enum

现在,你不应该只测试test And Names.Test2AndTest4是否非零,因为这并不能回答正确的问题。所以通常更好的习惯是先使用And运算符检查掩码,然后将其与掩码值进行比较,以确保所有位都被设置。


这样做明智吗?我无法想出用什么词来描述我对此的感受,但是将创建新的“Enum”条目与合并同一“Enum”中现有的“Enum”条目相结合似乎有些不妥。而且,即使我要这样做,而不是Test2AndTest4 = 10,我也应该只需执行Test2AndTest4 = Names.Test2 Or Names.Test4,对吧? - Interminable
1
@Interminable - 当某些选项的组合将异常常见时,通常会给它一个更方便的名称,而不是总是在使用点强制进行位操作。例如,在System.IO中的各种File *枚举中,大多数都有一个特定的ReadWrite成员命名。 - Damien_The_Unbeliever
经过一些阅读,看起来这种情况可能是例外而不是规则。如果我有一种特定的标志组合肯定会被经常重复使用,我可能会考虑它,但是现在ReadWrite是我能够找到的唯一示例。 - Interminable
@MattWilko 为什么新增枚举值会影响到其他东西?在上述示例中,添加额外标志位并进行测试,既不受“Option Strict On”的影响,也不受“Enum”本身或标志集合中其他值的影响,比如If test And Names.Test2(无论如何都不会起作用)或者CBool(test And Names.Test2)。除非我误解了这里说的内容?我应该补充说明,在你的回答中,采用确切数值的比较方式是我认为最好的方法,但我不明白为什么上述做法会影响任何一种方法。 - Interminable
@Interminable - 我理解你的观点,但是现有的标志值在未来可能会发生变化。确保您正在检查正确的内容的唯一方法是将其与标志进行比较。 - Matt Wilko
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接