Dấu ngã (~) trong định nghĩa enum là gì?


149

Tôi luôn ngạc nhiên rằng ngay cả sau khi sử dụng C # cho đến giờ, tôi vẫn tìm thấy những điều tôi chưa biết về ...

Tôi đã thử tìm kiếm trên internet để tìm cái này, nhưng sử dụng "~" trong tìm kiếm không hiệu quả với tôi và tôi cũng không tìm thấy gì trên MSDN (không nói là nó không có ở đó)

Tôi đã thấy đoạn mã này gần đây, dấu ngã (~) có nghĩa là gì?

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

Tôi hơi ngạc nhiên khi thấy nó vì vậy tôi đã cố gắng biên dịch nó, và nó đã hoạt động ... nhưng tôi vẫn không biết nó có nghĩa là gì / làm gì. Có ai giúp không ??


4
Đây là một giải pháp tuyệt vời và thanh lịch cho phép nâng cấp mượt mà enum theo thời gian. Thật không may, nó xung đột với CA-2217 và sẽ gây ra lỗi nếu bạn sử dụng phân tích mã :( msdn.microsoft.com/en-us/l Library / ms182335.aspx
Jason Coyne

Bây giờ là năm 2020 và tôi thấy mình đang nghĩ những từ giống như những từ mà bạn bắt đầu bài viết của mình. Vui mừng khi biết rằng tôi không cô đơn.
funkymushroom

Câu trả lời:


136

~ là toán tử bổ sung của một đơn vị - nó lật các bit của toán hạng của nó.

~0 = 0xFFFFFFFF = -1

trong số học bổ sung của hai, ~x == -x-1

toán tử ~ có thể được tìm thấy trong hầu hết mọi ngôn ngữ mượn cú pháp từ C, bao gồm Objective-C / C ++ / C # / Java / Javascript.


Thật tuyệt. Tôi đã không nhận ra bạn có thể làm điều đó trong một enum. Chắc chắn sẽ sử dụng trong tương lai
Orion Edwards

Vậy nó tương đương với All = Int32.MaxValue? Hoặc UInt32.MaxValue?
Joel Mueller

2
Tất cả = (không dấu int) -1 == UInt32.MaxValue. Int32.MaxValue không liên quan.
Jimmy

4
@ Stevo3000: Int32.MinValue là 0xF0000000, không phải là 0 (thực ra là ~ Int32.MaxValue)
Jimmy

2
@Jimmy: Int32.MinValue là 0x80000000. Nó chỉ có một tập bit duy nhất (không phải bốn bộ mà F sẽ cung cấp cho bạn).
ILMTitan

59

Tôi nghĩ rằng:

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

Sẽ rõ ràng hơn một chút.


10
Nó chắc chắn là như vậy. Hiệu quả tốt duy nhất của unary là nếu ai đó thêm vào bảng liệt kê, Tất cả tự động bao gồm nó. Tuy nhiên, lợi ích không vượt trội so với sự thiếu rõ ràng.
ctacke

12
Tôi không thấy làm thế nào rõ ràng hơn. Nó thêm hoặc dư thừa hoặc mơ hồ: "Tất cả" có nghĩa là "chính xác tập hợp của 3" hay "tất cả mọi thứ trong enum này"? Nếu tôi thêm một giá trị mới, tôi cũng nên thêm nó vào Tất cả? Nếu tôi thấy người khác chưa thêm giá trị mới cho Tất cả, đó có phải là chủ ý không? ~ 0 là rõ ràng.
Ken

16
Bỏ qua sở thích cá nhân, nếu ý nghĩa của ~ 0 rõ ràng với OP cũng như với bạn và tôi, anh ấy sẽ không bao giờ đặt ra câu hỏi này ngay từ đầu. Tôi không chắc những gì nói về sự rõ ràng của một phương pháp so với phương pháp khác.
Sean Bright

9
Vì một số lập trình viên sử dụng C # có thể chưa biết << nghĩa là gì, chúng ta cũng nên viết mã xung quanh điều đó để rõ ràng hơn? Tôi nghĩ là không.
Kurt Koller

4
@Paul, thật là điên rồ. Hãy ngừng sử dụng; bằng tiếng Anh vì nhiều người không hiểu cách sử dụng nó. Hoặc tất cả những từ họ không biết. Ồ, một nhóm toán tử nhỏ như vậy và chúng ta nên giảm mã cho những người không biết bit nào và cách thao tác với chúng?
Kurt Koller

22
public enum PurchaseMethod
{   
    All = ~0, // all bits of All are 1. the ~ operator just inverts bits
    None =  0,
    Cash =  1,
    Check =  2,
    CreditCard =  4
}

Do có hai phần bù trong C # ~0 == -1, nên số trong đó tất cả các bit là 1 trong biểu diễn nhị phân.


Có vẻ như họ đang tạo cờ bit cho phương thức thanh toán: 000 = Không; 001 = Tiền mặt; 010 = Kiểm tra; 100 = Thẻ tín dụng; 111 = Tất cả
Dillie-O

Đó không phải là phần bù của hai, đó là đảo ngược tất cả các bit và thêm một bit, do đó phần bù của hai bằng 0 vẫn là 0. ~ 0 chỉ đơn giản là đảo ngược tất cả các bit hoặc phần bù của một.
Beardo

Không, bổ sung của hai chỉ đơn giản là đảo ngược tất cả các bit.
cấu hình

2
@configurator - Điều đó không chính xác. Những cái bổ sung là một đảo ngược đơn giản của bit.
Dave Markle

c # sử dụng hai phần bù để biểu thị các giá trị âm. tất nhiên ~ không phải là hai phần bù, mà chỉ đơn giản là đảo ngược tất cả các bit. Tôi không chắc bạn đã lấy nó từ đâu, Beardo
Johannes Schaub - litb

17

Nó tốt hơn

All = Cash | Check | CreditCard

Giải pháp, bởi vì nếu bạn thêm phương thức khác sau, hãy nói:

PayPal = 8 ,

bạn sẽ được thực hiện với dấu ngã-Tất cả, nhưng phải thay đổi tất cả các dòng khác. Vì vậy, nó ít bị lỗi sau này.

Trân trọng


Sẽ tốt hơn nếu bạn cũng nói tại sao nó ít bị lỗi hơn. Giống như: nếu bạn lưu trữ giá trị trong tệp cơ sở dữ liệu / nhị phân và sau đó thêm một cờ khác vào bảng liệt kê, nó sẽ được bao gồm trong 'Tất cả', có nghĩa là 'Tất cả' sẽ luôn có nghĩa là tất cả, và không chỉ chừng nào cờ giống nhau :).
Aidiakapi

11

Chỉ cần một lưu ý phụ, khi bạn sử dụng

All = Cash | Check | CreditCard

bạn có lợi ích bổ sung Cash | Check | CreditCardsẽ đánh giá Allvà không với giá trị khác (-1) không bằng tất cả trong khi chứa tất cả các giá trị. Ví dụ: nếu bạn sử dụng ba hộp kiểm trong UI

[] Cash
[] Check
[] CreditCard

và tính tổng các giá trị của chúng và người dùng chọn tất cả chúng, bạn sẽ thấy Alltrong enum kết quả.


Đó là lý do tại sao bạn sử dụng myEnum.HasFlag()thay thế: D
Pyritie

@Pyritie: Làm thế nào để bình luận của bạn có liên quan đến những gì tôi nói?
cấu hình

như trong ... nếu bạn đã sử dụng ~ 0 cho "Tất cả", bạn có thể làm điều gì đó tương tự All.HasFlag(Cash | Check | CreditCard)và điều đó sẽ trở thành sự thật. Sẽ là một cách giải quyết vì ==không phải lúc nào cũng hoạt động với ~ 0.
Pyritie

Ồ, tôi đã nói về những gì bạn thấy trong trình gỡ lỗi và với ToString- không phải về việc sử dụng == All.
cấu hình

9

Đối với những người khác tìm thấy câu hỏi này chiếu sáng, tôi có một ~ví dụ nhanh để chia sẻ. Đoạn mã sau từ việc thực hiện phương pháp sơn, như được nêu chi tiết trong tài liệu Mono này , sử dụng ~rất hiệu quả:

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

Nếu không có ~toán tử, mã có thể sẽ trông giống như thế này:

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

... bởi vì bảng liệt kê trông như thế này:

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
}

Lưu ý sự giống nhau của enum với câu trả lời của Sean Bright?

Tôi nghĩ điều quan trọng nhất mang đến cho tôi là đó ~là cùng một toán tử trong một enum vì nó nằm trong một dòng mã thông thường.


Trong khối mã thứ hai của bạn, không nên &|s?
ClickRick

@ClickRick, cảm ơn vì đã bắt được. Khối mã thứ hai thực sự có ý nghĩa bây giờ.
Mike


1

Cách thay thế mà cá nhân tôi sử dụng, thực hiện điều tương tự câu trả lời của @Sean Bright nhưng có vẻ tốt hơn đối với tôi, là cái này:

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

Lưu ý cách bản chất nhị phân của các số đó, tất cả đều là lũy thừa của hai số, làm cho khẳng định sau đây đúng : (a + b + c) == (a | b | c). Và IMHO, có +vẻ tốt hơn.


1

Tôi đã thực hiện một số thử nghiệm với ~ và thấy rằng nó có thể có những cạm bẫy. Xem xét đoạn mã này cho LINQPad cho thấy giá trị Tất cả enum không hoạt động như mong đợi khi tất cả các giá trị được kết hợp với nhau.

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 
}

Tiêu chuẩn không nên nhận giá trị 0, nhưng giá trị lớn hơn 0.
Adrian Iftode

0

Chỉ muốn thêm, nếu bạn sử dụng [Cờ] enum, thì có thể thuận tiện hơn khi sử dụng toán tử dịch chuyển trái bit, như thế này:

[Flags]
enum SampleEnum
{
    None   = 0,      // 0
    First  = 1 << 0, // 1b    = 1d
    Second = 1 << 1, // 10b   = 2d
    Third  = 1 << 2, // 100b  = 4d
    Fourth = 1 << 3, // 1000b = 8d
    All    = ~0      // 11111111b
}

Điều này không trả lời câu hỏi nào cả.
Phục vụ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.