【问题标题】:What is the tilde (~) in the enum definition?枚举定义中的波浪号 (~) 是什么?
【发布时间】:2010-09-28 02:17:50
【问题描述】:

我总是很惊讶,即使在使用 C# 这么久之后,我仍然设法找到我不知道的东西......

我已尝试在 Internet 上搜索此内容,但在搜索中使用“~”对我来说效果不佳,而且我在 MSDN 上也没有找到任何东西(并不是说它不存在)

最近看到这个sn-p的代码,波浪号(~)是什么意思?

/// <summary>
/// Enumerates the ways a customer may purchase goods.
/// </summary>
[Flags]
public enum PurchaseMethod
{   
    All = ~0,
    None =  0,
    Cash =  1,
    Check =  2,
    CreditCard =  4
}

看到它我有点惊讶,所以我尝试编译它,它工作了......但我仍然不知道它的含义/作用。有什么帮助吗??

【问题讨论】:

  • 这是一个很棒且优雅的解决方案,允许随着时间的推移平滑升级枚举。不幸的是,它与 CA-2217 冲突,如果您使用代码分析会抛出错误:( msdn.microsoft.com/en-us/library/ms182335.aspx
  • 现在是 2020 年,我发现自己的想法与您开始发帖时的想法相同。很高兴听到我并不孤单。

标签: c# enums language-features enumeration


【解决方案1】:

[Flags] 枚举中的每个位表示启用 (1) 或禁用 (0)。
~ 运算符用于反转数字的所有位。示例:00001001b 变为 11110110b
因此~0 用于创建启用所有位的值,例如11111111b 用于 8 位枚举。

只是想补充一点,对于这种类型的枚举,使用按位左移运算符可能更方便,如下所示:

[Flags]
enum SampleEnum
{
    None   = 0,      // 0000b
    First  = 1 << 0, // 0001b
    Second = 1 << 1, // 0010b
    Third  = 1 << 2, // 0100b
    Fourth = 1 << 3, // 1000b
    All    = ~0      // 1111b
}

【讨论】:

  • 这根本不是在回答问题。
【解决方案2】:

对于发现这个问题很有启发性的其他人,我有一个快速的~ 示例可以分享。下面的sn-p来自一个paint方法的实现,详见this Mono documentation,使用~效果很好:

PaintCells (clipBounds, 
    DataGridViewPaintParts.All & ~DataGridViewPaintParts.SelectionBackground);

如果没有~ 运算符,代码可能看起来像这样:

PaintCells (clipBounds, DataGridViewPaintParts.Background 
    | DataGridViewPaintParts.Border
    | DataGridViewPaintParts.ContentBackground
    | DataGridViewPaintParts.ContentForeground
    | DataGridViewPaintParts.ErrorIcon
    | DataGridViewPaintParts.Focus);

...因为枚举看起来像这样:

public enum DataGridViewPaintParts
{
    None = 0,
    Background = 1,
    Border = 2,
    ContentBackground = 4,
    ContentForeground = 8,
    ErrorIcon = 16,
    Focus = 32,
    SelectionBackground = 64,
    All = 127 // which is equal to Background | Border | ... | Focus
}

注意到这个枚举与 Sean Bright 的答案相似吗?

我认为对我来说最重要的一点是 ~ 在枚举中与在普通代码行中的运算符相同。

【讨论】:

  • 在你的第二个代码块中,&amp;s 不应该是|s 吗?
  • @ClickRick,感谢您的关注。第二个代码块现在实际上是有意义的。
【解决方案3】:

我对 ~ 进行了一些实验,发现它可能有陷阱。考虑一下 LINQPad 的这个 sn-p,它表明当所有值一起进行运算时,All 枚举值的行为不符合预期。

void Main()
{
    StatusFilterEnum x = StatusFilterEnum.Standard | StatusFilterEnum.Saved;
    bool isAll = (x & StatusFilterEnum.All) == StatusFilterEnum.All;
    //isAll is false but the naive user would expect true
    isAll.Dump();
}
[Flags]
public enum StatusFilterEnum {
      Standard =0,
      Saved =1,   
      All = ~0 
}

【讨论】:

  • 标准不应获得 0 值,而是大于 0 的值。
【解决方案4】:

~ 是一元补码运算符——它翻转其操作数的位。

~0 = 0xFFFFFFFF = -1

在二进制补码算法中,~x == -x-1

在几乎所有从 C 中借用语法的语言中都可以找到 ~ 运算符,包括 Objective-C/C++/C#/Java/Javascript。

【讨论】:

  • 这很酷。我没有意识到你可以在枚举中做到这一点。以后一定会用到
  • 所以相当于All = Int32.MaxValue?还是 UInt32.MaxValue?
  • 所有 = (unsigned int)-1 == UInt32.MaxValue。 Int32.MaxValue 没有相关性。
  • @Stevo3000: Int32.MinValue 是 0xF0000000,不是~0(实际上是~Int32.MaxValue)
  • @Jimmy: Int32.MinValue 是 0x80000000。它只有一个位集(不是 F 给你的四个)。
【解决方案5】:

我个人使用的替代方法与@Sean Bright 的答案相同,但对我来说看起来更好,是这个:

[Flags]
public enum PurchaseMethod
{
    None = 0,
    Cash = 1,
    Check = 2,
    CreditCard = 4,
    PayPal = 8,
    BitCoin = 16,
    All = Cash + Check + CreditCard + PayPal + BitCoin
}

请注意这些数字的二进制性质,它们都是 2 的幂,如何使以下断言成立:(a + b + c) == (a | b | c)。恕我直言,+ 看起来更好。

【讨论】:

    【解决方案6】:

    只是一个旁注,当你使用时

    All = Cash | Check | CreditCard
    

    您有额外的好处,Cash | Check | CreditCard 将评估为All,而不是另一个不等于 all 同时包含所有值的值 (-1)。 例如,如果您在 UI 中使用三个复选框

    [] Cash
    [] Check
    [] CreditCard
    

    并将它们的值相加,然后用户将它们全部选中,您将在生成的枚举中看到 All

    【讨论】:

    • 这就是你使用myEnum.HasFlag() 的原因:D
    • @Pyritie:你的评论和我说的有什么关系?
    • 如...如果您使用 ~0 表示“全部”,您可以执行All.HasFlag(Cash | Check | CreditCard) 之类的操作,这将评估为真。将是一种解决方法,因为 == 并不总是适用于 ~0。
    • 哦,我说的是你在调试器和ToString 中看到的内容——而不是使用== All
    【解决方案7】:

    比那个好

    All = Cash | Check | CreditCard
    

    解决方案,因为如果你以后添加另一个方法,说:

    PayPal = 8 ,
    

    您已经完成了波浪号-All,但必须更改其他的全线。所以以后不容易出错。

    问候

    【讨论】:

    • 最好也说明为什么它不容易出错。喜欢:如果您将值存储在数据库/二进制文件中,然后将另一个标志添加到枚举中,它将包含在“全部”中,这意味着“全部”将始终表示全部,而不仅仅是只要标志是相同的:)。
    【解决方案8】:
    public enum PurchaseMethod
    {   
        All = ~0, // all bits of All are 1. the ~ operator just inverts bits
        None =  0,
        Cash =  1,
        Check =  2,
        CreditCard =  4
    }
    

    由于 C# 中的两个补码,~0 == -1,二进制表示中所有位为 1 的数字。

    【讨论】:

    • 看起来他们正在为付款方式创建一个位标志:000 = 无; 001 = 现金; 010 = 检查; 100 = 信用卡; 111 = 全部
    • 这不是二进制补码,而是将所有位反转并加一,因此 0 的二进制补码仍然是 0。~0 只是将所有位或一个补码反转。
    • 不,二进制补码只是将所有位取反。
    • @configurator -- 这是不正确的。个补码是位的简单反转。
    • c# 使用两个补码来表示负值。当然 ~ 不是两个补码,而是简单地将所有位反转。我不知道你从哪里得到的,比尔多
    【解决方案9】:

    我认为:

    [Flags]
    public enum PurchaseMethod
    {
        None = 0,
        Cash = 1,
        Check = 2,
        CreditCard = 4,
        All = Cash | Check | CreditCard
     }
    

    会更清楚一点。

    【讨论】:

    • 确实如此。一元的唯一好处是,如果有人添加到枚举中,All 会自动包含它。尽管如此,好处并没有超过缺乏清晰度。
    • 我不明白这有多清楚。它增加了冗余或歧义:“全部”是指“正是这 3 个的集合”还是“这个枚举中的所有内容”?如果我添加一个新值,我是否也应该将它添加到 All 中?如果我看到其他人没有为 All 添加新值,这是故意的吗? ~0 是明确的。
    • 个人偏好不谈,如果~0 的含义对 OP 和你我一样清楚,他一开始就不会提出这个问题。我不确定这说明了一种方法与另一种方法的清晰度。
    • 由于一些使用 C# 的程序员可能还不知道
    • @Paul,这有点疯狂。让我们停止使用 ;用英语,因为很多人不明白它的用法。或者所有那些他们不知道的词。哇,这么小的一组运算符,我们应该为那些不知道位是什么以及如何操作它们的人简化我们的代码?
    【解决方案10】:

    这是一个补码运算符, 这是我经常参考的关于按位运算符的文章

    http://www.blackwasp.co.uk/CSharpLogicalBitwiseOps.aspx

    msdn 在他们的枚举文章中也使用了它,证明它使用得更好

    http://msdn.microsoft.com/en-us/library/cc138362.aspx

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-29
      • 2011-09-22
      • 1970-01-01
      • 2020-08-08
      • 2011-05-02
      • 2012-10-10
      相关资源
      最近更新 更多