¿Cuál es la tilde (~) en la definición de enumeración?

149

Siempre me sorprende que incluso después de usar C # durante todo este tiempo, sigo encontrando cosas que no sabía ...

Intenté buscar esto en Internet, pero usar el "~" en una búsqueda no me funciona tan bien y tampoco encontré nada en MSDN (por no decir que no está allí)

Recientemente vi este fragmento de código, ¿qué significa 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
}

Me sorprendió un poco verlo, así que intenté compilarlo y funcionó ... pero todavía no sé lo que significa / hace. ¿¿Alguna ayuda??

Hugoware
fuente
44
Esta es una solución impresionante y elegante que permite una actualización sin problemas de la enumeración con el tiempo. Desafortunadamente, entra en conflicto con CA-2217 y arrojará un error si usa el análisis de código :( msdn.microsoft.com/en-us/library/ms182335.aspx
Jason Coyne
es 2020 ahora, y me encuentro pensando las mismas palabras con las que comienzas tu publicación. Me alegra saber que no estoy solo.
funkymushroom

Respuestas:

136

~ es el operador complementario del unario: voltea los bits de su operando.

~0 = 0xFFFFFFFF = -1

en la aritmética del complemento a dos, ~x == -x-1

El operador ~ se puede encontrar en casi cualquier lenguaje que tomó prestada la sintaxis de C, incluido Objective-C / C ++ / C # / Java / Javascript.

Palanqueta
fuente
Eso es genial. No me di cuenta de que podías hacer eso en una enumeración. Definitivamente lo
Orion Edwards
Entonces, ¿es el equivalente de All = Int32.MaxValue? O UInt32.MaxValue?
Joel Mueller
2
All = (unsigned int) -1 == UInt32.MaxValue. Int32.MaxValue no tiene relevancia.
Jimmy
44
@ Stevo3000: Int32.MinValue es 0xF0000000, que no es ~ 0 (en realidad es ~ Int32.MaxValue)
Jimmy
2
@Jimmy: Int32.MinValue es 0x80000000. Solo tiene un conjunto de bits (no los cuatro que F le daría).
ILMTitan
59

Yo pensaría que

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

Sería un poco más claro.

Sean Bright
fuente
10
Ciertamente lo es. El único efecto positivo del unario es que si alguien agrega a la enumeración, Todo lo incluye automáticamente. Aún así, el beneficio no supera la falta de claridad.
ctacke
12
No veo cómo eso está más claro. Agrega redundancia o ambigüedad: ¿"Todos" significa "exactamente el conjunto de estos 3" o "todo en esta enumeración"? Si agrego un nuevo valor, ¿también debo agregarlo a Todos? Si veo que alguien más no ha agregado un nuevo valor a Todos, ¿es intencional? ~ 0 es explícito.
Ken
16
Dejando a un lado la preferencia personal, si el significado de ~ 0 fuera tan claro para el OP como lo es para usted y para mí, él nunca habría planteado esta pregunta en primer lugar. No estoy seguro de lo que eso dice sobre la claridad de un enfoque versus el otro.
Sean Bright el
9
Dado que algunos programadores que usan C # podrían no saber qué significa <<, ¿deberíamos codificar también eso para mayor claridad? Yo creo que no.
Kurt Koller
44
@Paul, eso es un poco loco. Dejemos de usar; en inglés porque muchas personas no entienden su uso. O todas esas palabras que no saben. Wow, un conjunto tan pequeño de operadores, y deberíamos simplificar nuestro código para las personas que no saben qué son los bits y cómo manipularlos.
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
}

Debido a dos complementos en C #, ~0 == -1el número donde todos los bits son 1 en la representación binaria.

Johannes Schaub - litb
fuente
Parece que están creando un indicador de bits para el método de pago: 000 = Ninguno; 001 = efectivo; 010 = Verificar; 100 = tarjeta de crédito; 111 = Todos
Dillie-O
No es un complemento de dos, eso invierte todos los bits y agrega uno, de modo que el complemento de dos de 0 sigue siendo 0. El ~ 0 simplemente invierte todos los bits o el complemento de uno.
Beardo
No, el complemento de dos es simplemente invertir todos los bits.
configurador
2
@configurator: eso no es correcto. El complemento es una simple inversión de bits.
Dave Markle
c # usa dos complementos para representar valores negativos. por supuesto ~ no es un complemento de dos, sino que simplemente invierte todos los bits. no estoy seguro de dónde
sacaste
17

Es mejor que el

All = Cash | Check | CreditCard

solución, porque si agrega otro método más tarde, diga:

PayPal = 8 ,

ya habrá terminado con tilde-All, pero tendrá que cambiar la línea completa con la otra. Entonces es menos propenso a errores más tarde.

Saludos

blabla999
fuente
Es mejor si también dice por qué es menos propenso a errores. Me gusta: si almacena el valor en una base de datos / archivo binario y luego agrega otro indicador a la enumeración, se incluirá en 'Todos', lo que significa que 'Todos' siempre significará todo, y no solo mientras Las banderas son las mismas :).
Aidiakapi
11

Solo una nota al margen, cuando usas

All = Cash | Check | CreditCard

tiene el beneficio adicional de Cash | Check | CreditCardevaluar a Ally no a otro valor (-1) que no es igual a todos mientras contiene todos los valores. Por ejemplo, si usa tres casillas de verificación en la IU

[] Cash
[] Check
[] CreditCard

y sume sus valores, y el usuario los selecciona a todos, vería Allen la enumeración resultante.

configurador
fuente
Es por eso que usas myEnum.HasFlag()en su lugar: D
Pyritie
@Pyritie: ¿Cómo tiene algo que ver tu comentario con lo que dije?
Configurador
como en ... si All.HasFlag(Cash | Check | CreditCard)usaste ~ 0 para "Todos", podrías hacer algo así y eso se traduciría en verdadero. Sería una solución ya ==que no siempre funciona con ~ 0.
Pyritie
Estaba hablando de lo que ves en el depurador y con ToString, no sobre el uso == All.
Configurador
9

Para otros que encontraron esta pregunta esclarecedora, tengo un ~ejemplo rápido para compartir. El siguiente fragmento de la implementación de un método de pintura, como se detalla en esta documentación de Mono , se utiliza ~con gran efecto:

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

Sin el ~operador, el código probablemente se vería así:

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

... porque la enumeración se ve así:

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
}

¿Notan la similitud de esta enumeración con la respuesta de Sean Bright?

Creo que lo más importante para mí es que ~es el mismo operador en una enumeración que en una línea de código normal.

Miguel
fuente
En su segundo bloque de código, ¿no deberían &ser los |s?
ClickRick
@ClickRick, gracias por la captura. El segundo bloque de código en realidad tiene sentido ahora.
Mike
1

La alternativa que uso personalmente, que hace lo mismo que la respuesta de @Sean Bright pero me parece mejor, es esta:

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

Observe cómo la naturaleza binaria de esos números, que son todas las potencias de dos, hace la siguiente afirmación verdadera: (a + b + c) == (a | b | c). Y en mi humilde opinión, se +ve mejor.

Camilo Martin
fuente
1

He experimentado un poco con el ~ y descubro que podría tener dificultades. Considere este fragmento para LINQPad que muestra que el valor All enum no se comporta como se esperaba cuando todos los valores se ofrecen juntos.

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
fuente
Standard no debería obtener el valor 0, sino algo mayor que 0.
Adrian Iftode
0

Solo quiero agregar, si usa la enumeración [Flags], entonces puede ser más conveniente usar el operador de desplazamiento a la izquierda bit a bit, como este:

[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
fuente
Esto no responde a la pregunta en absoluto.
Servy