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??

Hugoware
źródło
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.

Jimmy
źródło
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.

Sean Bright
źródło
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.

Johannes Schaub - litb
źródło
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

blabla999
źródło
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.

konfigurator
źródło
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.

Mikrofon
źródło
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.

Camilo Martin
źródło
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 
}
Gavin
źródło
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
}
sad_robot
źródło
To wcale nie odpowiada na pytanie.
Servy