Co to jest tylda (~) w definicji wyliczenia?


149

Zawsze jestem zaskoczony, że nawet po używaniu C # przez cały ten czas wciąż udaje mi się znaleźć rzeczy, o których nie wiedziałem ...

Próbowałem poszukać tego w Internecie, ale użycie „~” w wyszukiwaniu nie działa tak dobrze i nie znalazłem również niczego w MSDN (nie mówiąc, że go tam nie ma)

Widziałem ostatnio ten fragment kodu, co oznacza tylda (~)?

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

Byłem trochę zaskoczony, że to zobaczyłem, więc próbowałem to skompilować i zadziałało ... ale nadal nie wiem, co to znaczy / robi. Jakaś pomoc??


4
To niesamowite i eleganckie rozwiązanie pozwalające na płynne uaktualnianie wyliczenia w czasie. Niestety jest w konflikcie z CA-2217 i jeśli użyjesz analizy kodu, wygeneruje błąd
— Jason Coyne

teraz jest rok 2020 i myślę te same słowa, jak te, od których zaczynasz swój post. Miło słyszeć, że nie jestem sam.
— funkymushroom

Odpowiedzi:


136

~ jest jednoargumentowym operatorem dopełniacza jedynki - odwraca bity swojego operandu.

~0 = 0xFFFFFFFF = -1

w arytmetyce uzupełnień do dwóch, ~x == -x-1

operator ~ można znaleźć w prawie każdym języku, który zapożyczył składnię z C, w tym w Objective-C / C ++ / C # / Java / Javascript.


To super. Nie zdawałem sobie sprawy, że możesz to zrobić w enum. Na pewno użyję w przyszłości
— Orion Edwards,

Więc jest to odpowiednik All = Int32.MaxValue? Lub UInt32.MaxValue?
— Joel Mueller,

2
All = (unsigned int) -1 == UInt32.MaxValue. Int32.MaxValue nie ma znaczenia.
— Jimmy,

4
@ Stevo3000: Int32.MinValue to 0xF0000000, czyli nie ~ 0 (w rzeczywistości jest to ~ Int32.MaxValue)
— Jimmy

2
@Jimmy: Int32.MinValue to 0x80000000. Ma tylko jeden zestaw bitów (nie cztery, które dałby ci F).
— ILMTitan

59

Myślę, że:

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

Byłoby trochę bardziej jasne.


10
To z pewnością jest. Jedynym dobrym efektem unarnika jest to, że jeśli ktoś dodaje do wyliczenia, to wszystko automatycznie włącza. Mimo to korzyści nie przeważają nad brakiem jasności.
— ctacke

12
Nie rozumiem, jak to jest bardziej jasne. Dodaje albo nadmiarowość, albo niejednoznaczność: czy „Wszystko” oznacza „dokładnie zestaw tych 3” czy „wszystko w tym wyliczeniu”? Jeśli dodam nową wartość, czy powinienem również dodać ją do wszystkich? Jeśli widzę, że ktoś inny nie dodał nowej wartości do wszystkich, czy jest to zamierzone? ~ 0 jest jawne.
— Ken

16
Pomijając osobiste preferencje, gdyby znaczenie ~ 0 było tak jasne dla OP, jak dla ciebie i dla mnie, nigdy nie zadałby tego pytania. Nie jestem pewien, co to mówi o przejrzystości jednego podejścia w porównaniu z drugim.
— Sean Bright

9
Ponieważ niektórzy programiści używający języka C # mogą jeszcze nie wiedzieć, co oznacza <<, czy powinniśmy również kodować wokół tego, aby uzyskać większą jasność? Myślę, że nie.
— Kurt Koller,

4
@Paul, to trochę szalone. Przestańmy używać; w języku angielskim, ponieważ wiele osób nie rozumie jego użycia. Albo te wszystkie słowa, których nie znają. Wow, taki mały zestaw operatorów i powinniśmy ograniczyć nasz kod dla ludzi, którzy nie wiedzą, czym są bity i jak nimi manipulować?
— 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
}

Z powodu dwóch uzupełnień w C #, ~0 == -1liczba, w której wszystkie bity mają wartość 1 w reprezentacji binarnej.


Wygląda na to, że tworzą flagę bitową dla metody płatności: 000 = brak; 001 = gotówka; 010 = Sprawdź; 100 = karta kredytowa; 111 = All
— Dillie-O

To nie jest uzupełnienie do dwóch, to jest odwrócenie wszystkich bitów i dodanie jednego, tak aby uzupełnienie do dwóch było nadal 0. ~ 0 po prostu odwraca wszystkie bity lub jedno uzupełnienie.
— Beardo

Nie, dopełnienie do dwóch polega po prostu na odwróceniu wszystkich bitów.
— konfigurator

2
@configurator - to nie jest poprawne. Jedynym dopełnieniem jest prosta inwersja bitów.
— Dave Markle

c # używa dwóch dopełnień do reprezentowania wartości ujemnych. oczywiście ~ nie jest dwoma dopełnieniami, ale po prostu odwraca wszystkie bity. Nie jestem pewien, skąd to masz, Beardo
— Johannes Schaub - litb

17

Jest lepszy niż

All = Cash | Check | CreditCard

rozwiązanie, ponieważ jeśli później dodasz inną metodę, powiedz:

PayPal = 8 ,

skończysz już z tyldą-All, ale musisz zmienić linię all-line z drugą. Więc później jest mniej podatny na błędy.

pozdrowienia


Lepiej, jeśli powiesz również, dlaczego jest mniej podatny na błędy. Na przykład: jeśli zapiszesz wartość w bazie danych / pliku binarnym, a następnie dodasz kolejną flagę do wyliczenia, zostanie ona uwzględniona w polu „Wszystkie”, co oznacza, że ​​„Wszystko” będzie zawsze oznaczać wszystko, a nie tylko flagi są takie same :).
— Aidiakapi

11

Tylko uwaga na marginesie, kiedy używasz

All = Cash | Check | CreditCard

masz dodatkową korzyść, która Cash | Check | CreditCardbyłaby szacowana na Allinną wartość (-1), a nie na inną wartość (-1), która nie jest równa all, ale zawiera wszystkie wartości. Na przykład, jeśli używasz trzech pól wyboru w interfejsie użytkownika

[] Cash
[] Check
[] CreditCard

i zsumuj ich wartości, a użytkownik wybierze je wszystkie, zobaczysz Allw wynikowym wyliczeniu.


Dlatego myEnum.HasFlag()zamiast tego używasz : D
— Pyritie

@Pyritie: Jak twój komentarz ma coś wspólnego z tym, co powiedziałem?
— konfigurator

jak w… jeśli użyjesz ~ 0 dla „Wszystkie”, możesz zrobić coś podobnego, All.HasFlag(Cash | Check | CreditCard)a to będzie ewaluować jako prawda. Byłoby obejściem, ponieważ ==nie zawsze działa z ~ 0.
— Pyritie

Och, mówiłem o tym, co widzisz w debugerze, a ToStringnie o używaniu == All.
— konfigurator

9

Dla innych, którzy uznali to pytanie za pouczające, mam szybki ~przykład do udostępnienia. Poniższy fragment z implementacji metody malowania, jak opisano szczegółowo w tej dokumentacji Mono , wykorzystuje ~z doskonałym efektem:

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

Bez ~operatora kod prawdopodobnie wyglądałby mniej więcej tak:

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

... bo wyliczenie wygląda tak:

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
}

Zwróć uwagę na podobieństwo tego wyliczenia do odpowiedzi Seana Brighta?

Myślę, że najważniejszym dla mnie wnioskiem jest to, że ~jest to ten sam operator w wyliczeniu, co w normalnym wierszu kodu.


Czy w drugim bloku kodu nie powinno &być |s?
— ClickRick,

@ClickRick, dzięki za haczyk. Drugi blok kodu ma teraz sens.
— Mike


1

Alternatywą, której osobiście używam, a która robi to samo, co odpowiedź @Sean Bright, ale dla mnie wygląda lepiej, jest ta:

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

Wskazówki jak binarny charakter tych liczb, które są potęgami dwójki, wydaje następujące twierdzenie prawdziwe: (a + b + c) == (a | b | c). I IMHO, +wygląda lepiej.


1

Poeksperymentowałem z ~ i stwierdziłem, że może mieć pułapki. Rozważmy ten fragment kodu LINQPad, który pokazuje, że wartość wyliczenia All nie zachowuje się zgodnie z oczekiwaniami, gdy wszystkie wartości są razem.

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 
}

Standard nie powinien otrzymać wartości 0, ale coś większego niż 0.
— Adrian Iftode,

0

Po prostu chcę dodać, jeśli używasz wyliczenia [Flags], wygodniejsze może być użycie bitowego operatora przesunięcia w lewo, na przykład:

[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
}

To wcale nie odpowiada na pytanie.
— Servy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.