Quel est le tilde (~) dans la définition enum?

149

Je suis toujours surpris que même après avoir utilisé C # pendant tout ce temps maintenant, j'arrive toujours à trouver des choses que je ne savais pas ...

J'ai essayé de chercher sur Internet pour cela, mais utiliser le "~" dans une recherche ne fonctionne pas très bien pour moi et je n'ai rien trouvé sur MSDN non plus (pour ne pas dire qu'il n'y est pas)

J'ai vu cet extrait de code récemment, que signifie le 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
}

J'ai été un peu surpris de le voir alors j'ai essayé de le compiler, et cela a fonctionné ... mais je ne sais toujours pas ce que cela signifie / fait. De l'aide??

Hugoware
la source
4
Il s'agit d'une solution impressionnante et élégante permettant une mise à niveau en douceur de l'énumération au fil du temps. Malheureusement, il entre en conflit avec CA-2217 et générera
Jason Coyne
son 2020 maintenant, et je me surprends à penser les mêmes mots que ceux avec lesquels vous commencez votre message. Heureux d'apprendre que je ne suis pas seul.
funkymushroom

Réponses:

136

~ est l'opérateur de complément à un unaire - il retourne les bits de son opérande.

~0 = 0xFFFFFFFF = -1

en arithmétique complémentaire à deux, ~x == -x-1

l'opérateur ~ peut être trouvé dans à peu près n'importe quel langage qui a emprunté la syntaxe de C, y compris Objective-C / C ++ / C # / Java / Javascript.

Jimmy
la source
C'est super. Je ne savais pas que vous pouviez faire cela dans une énumération. Sera certainement utilisé à l'avenir
Orion Edwards
C'est donc l'équivalent de All = Int32.MaxValue? Ou UInt32.MaxValue?
Joel Mueller
2
Tout = (entier non signé) -1 == UInt32.MaxValue. Int32.MaxValue n'a aucune pertinence.
Jimmy
4
@ Stevo3000: Int32.MinValue est 0xF0000000, ce qui n'est pas ~ 0 (c'est en fait ~ Int32.MaxValue)
Jimmy
2
@Jimmy: Int32.MinValue est 0x80000000. Il n'a qu'un seul bit défini (pas les quatre que F vous donnerait).
ILMTitan
59

Je pense que:

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

Ce serait un peu plus clair.

Sean Bright
la source
10
C'est certainement. Le seul bon effet de l'unaire est que si quelqu'un ajoute à l'énumération, Tous l'inclut automatiquement. Pourtant, l'avantage ne l'emporte pas sur le manque de clarté.
ctacke
12
Je ne vois pas comment c'est plus clair. Cela ajoute soit de la redondance, soit de l'ambiguïté: "Tout" signifie-t-il "exactement l'ensemble de ces 3" ou "tout dans cette énumération"? Si j'ajoute une nouvelle valeur, dois-je également l'ajouter à Tout? Si je vois que quelqu'un d'autre n'a pas ajouté une nouvelle valeur à Tout, est-ce intentionnel? ~ 0 est explicite.
Ken le
16
Hormis les préférences personnelles, si la signification de ~ 0 était aussi claire pour le PO que pour vous et moi, il n'aurait jamais posé cette question en premier lieu. Je ne sais pas ce que cela dit de la clarté d'une approche par rapport à l'autre.
Sean Bright
9
Étant donné que certains programmeurs qui utilisent C # ne savent peut-être pas encore ce que << signifie, devrions-nous également coder autour de cela, pour plus de clarté? Je crois que non.
Kurt Koller
4
@Paul, c'est un peu fou. Arrêtons d'utiliser; en anglais parce que beaucoup de gens ne comprennent pas son utilisation. Ou tous ces mots qu'ils ne connaissent pas. Wow, un si petit ensemble d'opérateurs, et nous devrions abattre notre code pour les personnes qui ne savent pas ce que sont les bits et comment les manipuler?
Kurt Koller le
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
}

En raison de deux complément en C #, ~0 == -1le nombre où tous les bits sont 1 dans la représentation binaire.

Johannes Schaub - litb
la source
On dirait qu'ils créent un indicateur de bit pour le mode de paiement: 000 = Aucun; 001 = espèces; 010 = Vérifier; 100 = carte de crédit; 111 = All
Dillie-O
Ce n'est pas un complément à deux, c'est inverser tous les bits et en ajouter un, de sorte que le complément à deux de 0 soit toujours 0. Le ~ 0 inverse simplement tous les bits ou son complément.
Beardo
Non, le complément à deux inverse simplement tous les bits.
configurateur
2
@configurator - ce n'est pas correct. Le complément à uns est une simple inversion de bits.
Dave Markle
c # utilise deux compléments pour représenter les valeurs négatives. bien sûr, ~ n'est pas deux compléments, mais inverse simplement tous les bits. Je ne sais pas d'où vous avez obtenu cela, Beardo
Johannes Schaub - litb
17

C'est mieux que le

All = Cash | Check | CreditCard

solution, car si vous ajoutez une autre méthode plus tard, dites:

PayPal = 8 ,

vous en aurez déjà fini avec le tilde-All, mais vous devrez changer le all-line avec l'autre. Donc, c'est moins sujet aux erreurs plus tard.

Cordialement

blabla999
la source
C'est mieux si vous dites également pourquoi c'est moins sujet aux erreurs. Comme: si vous stockez la valeur dans une base de données / fichier binaire puis ajoutez un autre indicateur à l'énumération, il serait inclus dans le 'Tout', ce qui signifie que 'Tout' signifiera toujours tout, et pas aussi longtemps que le les drapeaux sont les mêmes :).
Aidiakapi
11

Juste une note latérale, lorsque vous utilisez

All = Cash | Check | CreditCard

vous avez l'avantage supplémentaire qui Cash | Check | CreditCardserait évalué à Allet non à une autre valeur (-1) qui n'est pas égale à tout tout en contenant toutes les valeurs. Par exemple, si vous utilisez trois cases à cocher dans l'interface utilisateur

[] Cash
[] Check
[] CreditCard

et additionnez leurs valeurs, et l'utilisateur les sélectionne toutes, vous verriez Alldans l'énumération résultante.

configurateur
la source
C'est pourquoi vous utilisez à la myEnum.HasFlag()place: D
Pyritie
@Pyritie: En quoi votre commentaire a-t-il quelque chose à voir avec ce que j'ai dit?
configurateur
comme dans ... si vous All.HasFlag(Cash | Check | CreditCard)utilisiez ~ 0 pour "Tous", vous pourriez faire quelque chose comme et cela évaulerait à vrai. Serait une solution de contournement car ==ne fonctionne pas toujours avec ~ 0.
Pyritie
Oh, je parlais de ce que vous voyez dans le débogueur et avec ToString- pas de l'utilisation == All.
configurateur
9

Pour ceux qui ont trouvé cette question éclairante, j'ai un ~exemple rapide à partager. L'extrait suivant de l'implémentation d'une méthode de peinture, comme détaillé dans cette documentation Mono , utilise ~à bon escient:

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

Sans l' ~opérateur, le code ressemblerait probablement à ceci:

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

... parce que l'énumération ressemble à ceci:

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
}

Remarquez la similitude de cette énumération avec la réponse de Sean Bright?

Je pense que le plus important à retenir pour moi est que ~c'est le même opérateur dans une énumération que dans une ligne de code normale.

Mike
la source
Dans votre deuxième bloc de code, les &s ne devraient-ils pas être des |s?
ClickRick
@ClickRick, merci pour la capture. Le deuxième bloc de code a du sens maintenant.
Mike
1

L'alternative que j'utilise personnellement, qui fait la même chose que la réponse de @Sean Bright mais me semble meilleure, est celle-ci:

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

Remarquez comment la nature binaire de ces chiffres, qui sont tous les pouvoirs de deux, fait l'affirmation suivante vraie: (a + b + c) == (a | b | c). Et à mon humble avis, +semble mieux.

Camilo Martin
la source
1

J'ai fait quelques expériences avec le ~ et je trouve qu'il pourrait avoir des pièges. Considérez cet extrait de code pour LINQPad qui montre que la valeur d'énumération All ne se comporte pas comme prévu lorsque toutes les valeurs sont regroupées.

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
la source
Standard ne devrait pas avoir la valeur 0, mais quelque chose de plus grand que 0.
Adrian Iftode
0

Je veux juste ajouter, si vous utilisez [Flags] enum, alors il peut être plus pratique d'utiliser l'opérateur de décalage gauche au niveau du bit, comme ceci:

[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
}
triste_robot
la source
Cela ne répond pas du tout à la question.
Servy le