Was ist die Tilde (~) in der Aufzählungsdefinition?

149

Ich bin immer wieder überrascht, dass es mir auch nach all der Zeit, in der ich C # verwendet habe, gelingt, Dinge zu finden, von denen ich nichts wusste ...

Ich habe versucht, im Internet danach zu suchen, aber die Verwendung des "~" in einer Suche funktioniert bei mir nicht so gut und ich habe auch nichts auf MSDN gefunden (um nicht zu sagen, dass es nicht da ist).

Ich habe diesen Codeausschnitt kürzlich gesehen. Was bedeutet die Tilde (~)?

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

Ich war ein wenig überrascht, es zu sehen, also habe ich versucht, es zu kompilieren, und es hat funktioniert ... aber ich weiß immer noch nicht, was es bedeutet / tut. Irgendeine Hilfe??

Hugoware
quelle
4
Dies ist eine fantastische und elegante Lösung, die eine reibungslose Aktualisierung der Aufzählung im Laufe der Zeit ermöglicht. Leider widerspricht es CA-2217 und wird einen Fehler auslösen,
Jason Coyne
Es ist jetzt 2020 und ich denke an die gleichen Worte wie die, mit denen Sie Ihren Beitrag beginnen. Freut mich zu hören, dass ich nicht alleine bin.
Funkymushroom

Antworten:

136

~ ist der unäre Komplementoperator - er dreht die Bits seines Operanden um.

~0 = 0xFFFFFFFF = -1

in Zweierkomplementarithmetik, ~x == -x-1

Der Operator ~ kann in nahezu jeder Sprache gefunden werden, die Syntax von C entlehnt hat, einschließlich Objective-C / C ++ / C # / Java / Javascript.

Jimmy
quelle
Das ist cool. Ich wusste nicht, dass du das in einer Aufzählung machen kannst. Wird definitiv in Zukunft verwendet
Orion Edwards
Also ist es das Äquivalent von All = Int32.MaxValue? Oder UInt32.MaxValue?
Joel Mueller
2
All = (unsigned int) -1 == UInt32.MaxValue. Int32.MaxValue hat keine Relevanz.
Jimmy
4
@ Stevo3000: Int32.MinValue ist 0xF0000000, was nicht ~ 0 ist (es ist tatsächlich ~ Int32.MaxValue)
Jimmy
2
@ Jimmy: Int32.MinValue ist 0x80000000. Es ist nur ein einziges Bit gesetzt (nicht die vier, die F Ihnen geben würde).
ILMTitan
59

Ich würde das denken:

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

Wäre etwas klarer.

Sean Bright
quelle
10
Es ist sicherlich. Der einzig gute Effekt von Unary ist, dass, wenn jemand die Aufzählung hinzufügt, All sie automatisch einschließt. Dennoch überwiegt der Nutzen nicht die mangelnde Klarheit.
ctacke
12
Ich sehe nicht, wie das klarer ist. Es fügt entweder Redundanz oder Mehrdeutigkeit hinzu: Bedeutet "Alle" "genau die Menge dieser 3" oder "alles in dieser Aufzählung"? Wenn ich einen neuen Wert hinzufüge, sollte ich ihn auch zu All hinzufügen? Wenn ich sehe, dass jemand anderes All keinen neuen Wert hinzugefügt hat , ist das beabsichtigt? ~ 0 ist explizit.
Ken
16
Abgesehen von der persönlichen Präferenz hätte er diese Frage niemals gestellt, wenn die Bedeutung von ~ 0 für das OP genauso klar gewesen wäre wie für Sie und mich. Ich bin mir nicht sicher, was das über die Klarheit eines Ansatzes gegenüber dem anderen aussagt.
Sean Bright
9
Da einige Programmierer, die C # verwenden, möglicherweise noch nicht wissen, was << bedeutet, sollten wir dies auch zur besseren Übersichtlichkeit codieren? Ich denke nicht.
Kurt Koller
4
@ Paul, das ist irgendwie verrückt. Hören wir auf zu benutzen; auf Englisch, weil viele Leute seine Verwendung nicht verstehen. Oder all diese Wörter, die sie nicht kennen. Wow, so eine kleine Gruppe von Operatoren, und wir sollten unseren Code für die Leute, die nicht wissen, was Bits sind und wie man sie manipuliert, dumm machen?
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
}

Aufgrund von zwei Komplementen in C # ist ~0 == -1die Zahl, bei der alle Bits in der Binärdarstellung 1 sind.

Johannes Schaub - litb
quelle
Sieht so aus, als würden sie ein Bit-Flag für die Zahlungsmethode erstellen: 000 = Keine; 001 = Bargeld; 010 = Prüfen; 100 = Kreditkarte; 111 = Alle
Dillie-O
Es ist nicht das Zweierkomplement, das alle Bits invertiert und eins addiert, so dass das Zweierkomplement von 0 immer noch 0 ist. Die ~ 0 invertiert einfach alle Bits oder das eigene Komplement.
Beardo
Nein, das Zweierkomplement invertiert einfach alle Bits.
Konfigurator
2
@configurator - das stimmt nicht. Das Einerkomplement ist eine einfache Inversion von Bits.
Dave Markle
c # verwendet zwei Komplemente, um negative Werte darzustellen. Natürlich ist ~ kein Zweierkomplement, sondern invertiert einfach alle Bits. Ich bin mir nicht sicher, woher du das hast, Beardo
Johannes Schaub - litb
17

Es ist besser als das

All = Cash | Check | CreditCard

Lösung, denn wenn Sie später eine andere Methode hinzufügen, sagen Sie:

PayPal = 8 ,

Sie sind bereits mit der Tilde-All fertig, müssen aber die All-Line mit der anderen ändern. So ist es später weniger fehleranfällig.

Grüße

blabla999
quelle
Es ist besser, wenn Sie auch sagen, warum es weniger fehleranfällig ist. Beispiel: Wenn Sie den Wert in einer Datenbank- / Binärdatei speichern und dann der Aufzählung ein weiteres Flag hinzufügen, wird er in "Alle" aufgenommen, was bedeutet, dass "Alle" immer "Alle" bedeutet und nicht nur so lange wie die Flaggen sind gleich :).
Aidiakapi
11

Nur eine Randnotiz, wenn Sie verwenden

All = Cash | Check | CreditCard

Sie haben den zusätzlichen Vorteil, dass ein anderer Wert (-1) Cash | Check | CreditCardbewertet Allwird, der nicht allen gleich ist, während alle Werte enthalten sind. Wenn Sie beispielsweise drei Kontrollkästchen in der Benutzeroberfläche verwenden

[] Cash
[] Check
[] CreditCard

und summieren Sie ihre Werte, und der Benutzer wählt sie alle aus, die Sie Allin der resultierenden Aufzählung sehen würden.

Konfigurator
quelle
Deshalb verwenden Sie myEnum.HasFlag()stattdessen: D
Pyritie
@ Pyritie: Wie hat dein Kommentar etwas mit dem zu tun, was ich gesagt habe?
Konfigurator
wie in ... wenn du ~ 0 für "Alle" verwendest, könntest du so etwas tun All.HasFlag(Cash | Check | CreditCard)und das würde sich als wahr herausstellen. Wäre eine Problemumgehung, da ==dies nicht immer mit ~ 0 funktioniert.
Pyritie
Oh, ich habe darüber gesprochen, was Sie im Debugger sehen und mit ToString- nicht über die Verwendung == All.
Konfigurator
9

Für andere, die diese Frage aufschlussreich fanden, habe ich ein kurzes ~Beispiel. Der folgende Ausschnitt aus der Implementierung einer Malmethode, wie in dieser Mono-Dokumentation beschrieben , wird ~mit großer Wirkung verwendet:

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

Ohne den ~Operator würde der Code wahrscheinlich ungefähr so ​​aussehen:

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

... weil die Aufzählung so aussieht:

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
}

Beachten Sie die Ähnlichkeit dieser Aufzählung mit der Antwort von Sean Bright?

Ich denke, das Wichtigste für mich ist, dass ~es sich bei einer Aufzählung um denselben Operator handelt wie in einer normalen Codezeile.

Mike
quelle
Sollte das &s in Ihrem zweiten Codeblock nicht s sein |?
ClickRick
@ClickRick, danke für den Fang. Der zweite Codeblock macht jetzt tatsächlich Sinn.
Mike
1

Die Alternative, die ich persönlich benutze und die das Gleiche tut wie die Antwort von @Sean Bright, aber für mich besser aussieht, ist folgende:

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

Beachten Sie, wie die binäre Natur dieser Zahlen, die alle Zweierpotenzen sind, die folgende Behauptung wahr macht : (a + b + c) == (a | b | c). Und meiner Meinung nach +sieht es besser aus.

Camilo Martin
quelle
1

Ich habe einige Experimente mit dem ~ durchgeführt und festgestellt, dass es Fallstricke haben könnte. Betrachten Sie dieses Snippet für LINQPad, das zeigt, dass sich der Wert All enum nicht wie erwartet verhält, wenn alle Werte zusammen angeordnet sind.

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
quelle
Standard sollte nicht den 0-Wert bekommen, sondern etwas größer als 0.
Adrian Iftode
0

Wenn Sie nur die Aufzählung [Flags] verwenden möchten, ist es möglicherweise bequemer, den bitweisen Linksverschiebungsoperator wie folgt zu verwenden:

[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
quelle
Dies beantwortet die Frage überhaupt nicht.
Servy