Qual é o til (~) na definição de enum?

149

Fico sempre surpreso que, mesmo depois de usar o C # por todo esse tempo, ainda consigo encontrar coisas que não sabia ...

Tentei pesquisar na Internet, mas usar o "~" em uma pesquisa não está funcionando tão bem para mim e também não encontrei nada no MSDN (para não dizer que não está lá)

Eu vi esse trecho de código recentemente, o que significa o til (~)?

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

Fiquei um pouco surpreso ao vê-lo, então tentei compilá-lo e funcionou ... mas ainda não sei o que isso significa / faz. Qualquer ajuda??

Hugoware
fonte
4
Esta é uma solução impressionante e elegante que permite uma atualização suave do enum ao longo do tempo. Infelizmente ele entra em conflito com CA-2217 e irá lançar um erro se você usar a análise de código :( msdn.microsoft.com/en-us/library/ms182335.aspx
Jason Coyne
agora é 2020 e eu me pego pensando nas mesmas palavras com as quais você inicia sua postagem. Fico feliz em saber que não estou sozinha.
funkymushroom

Respostas:

136

~ é o operador complementar do unário - ele inverte os bits do seu operando.

~0 = 0xFFFFFFFF = -1

na aritmética do complemento de dois, ~x == -x-1

o operador ~ pode ser encontrado em praticamente qualquer linguagem que emprestou sintaxe de C, incluindo Objective-C / C ++ / C # / Java / Javascript.

Jimmy
fonte
Isso é legal. Não sabia que você poderia fazer isso em um enum. Definitivamente usará no futuro
Orion Edwards
Portanto, é o equivalente a All = Int32.MaxValue? Ou UInt32.MaxValue?
Joel Mueller
2
Tudo = (int não assinado) -1 == UInt32.MaxValue. Int32.MaxValue não tem relevância.
Jimmy
4
@ Stevo3000: Int32.MinValue é 0xF0000000, que não é ~ 0 (é realmente ~ Int32.MaxValue)
Jimmy
2
@ Jimmy: Int32.MinValue é 0x80000000. Ele tem apenas um único conjunto de bits (não os quatro que F daria a você).
ILMTitan
59

Eu pensaria que:

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

Seria um pouco mais claro.

Sean Bright
fonte
10
Certamente é. O único efeito bom do unário é que, se alguém adiciona à enumeração, Tudo o inclui automaticamente. Ainda assim, o benefício não supera a falta de clareza.
Ctacke
12
Não vejo como isso é mais claro. Acrescenta redundância ou ambiguidade: "Todos" significam "exatamente o conjunto desses 3" ou "tudo neste enum"? Se eu adicionar um novo valor, também devo adicioná-lo a Todos? Se vejo que alguém não adicionou um novo valor a Todos, isso é intencional? ~ 0 é explícito.
Ken
16
Preferência pessoal à parte, se o significado de ~ 0 fosse tão claro para o OP quanto para você e eu, ele nunca teria colocado essa questão em primeiro lugar. Não sei o que isso diz sobre a clareza de uma abordagem em relação à outra.
Sean Bright #
9
Como alguns programadores que usam C # talvez ainda não saibam o que significa <<, devemos codificar isso também, para maior clareza? Eu acho que não.
Kurt Koller
4
@Paul, isso é meio louco. Vamos parar de usar; em inglês porque muitas pessoas não entendem seu uso. Ou todas aquelas palavras que eles não sabem. Uau, um conjunto tão pequeno de operadores, e devemos embaçar nosso código para as pessoas que não sabem o que são bits e como manipulá-los?
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
}

Por causa de dois complementos em C #, ~0 == -1o número em que todos os bits são 1 na representação binária.

Johannes Schaub - litb
fonte
Parece que eles estão criando um sinalizador de bit para o método de pagamento: 000 = Nenhum; 001 = dinheiro; 010 = Cheque; 100 = cartão de crédito; 111 = All
Dillie-O
Não é o complemento de dois, é inverter todos os bits e adicionar um, para que o complemento de dois ainda seja 0. O ~ 0 simplesmente inverte todos os bits ou o complemento de alguém.
Beardo
Não, o complemento de dois é simplesmente inverter todos os bits.
Configurator
2
@ configurador - isso não está correto. Os complementos são uma simples inversão de bits.
Dave Markle
O c # usa dois complementos para representar valores negativos. é claro que ~ não é um complemento de dois, mas simplesmente inverte todos os bits. Não sei de onde você tirou isso, Beardo
Johannes Schaub - litb
17

É melhor que o

All = Cash | Check | CreditCard

solução, porque se você adicionar outro método posteriormente, diga:

PayPal = 8 ,

você já estará pronto com o til-All, mas precisará alterar o all-line com o outro. Portanto, é menos propenso a erros mais tarde.

Saudações

blabla999
fonte
É melhor se você também disser por que é menos propenso a erros. Como: se você armazenar o valor em um banco de dados / arquivo binário e, em seguida, adicionar outro sinalizador à enumeração, ele será incluído no 'All', o que significa que 'All' sempre significará tudo, e não apenas enquanto sinalizadores são os mesmos :).
Aidiakapi
11

Apenas uma nota lateral, quando você usa

All = Cash | Check | CreditCard

você tem o benefício adicional que Cash | Check | CreditCardseria avaliado Alle não com outro valor (-1) que não é igual a todos e contém todos os valores. Por exemplo, se você usar três caixas de seleção na interface do usuário

[] Cash
[] Check
[] CreditCard

e some seus valores, e o usuário seleciona todos eles, você verá Allna enumeração resultante.

configurador
fonte
É por isso que você usa myEnum.HasFlag(): D
Pyritie 7/11/12
@Pyritie: Como o seu comentário tem algo a ver com o que eu disse?
configurator
como em ... se você usasse ~ 0 para "Todos", você poderia fazer algo parecido All.HasFlag(Cash | Check | CreditCard)e isso seria eva verdade. Seria uma solução alternativa, pois ==nem sempre funciona com ~ 0.
Pyritie
Ah, eu estava falando sobre o que você vê no depurador e com ToString- não sobre o uso == All.
configurator
9

Para outros que acharam essa pergunta esclarecedora, tenho um ~exemplo rápido para compartilhar. O seguinte trecho da implementação de um método de pintura, conforme detalhado nesta documentação Mono , usa ~com grande efeito:

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

Sem o ~operador, o código provavelmente seria algo como isto:

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

... porque a enumeração se parece com isso:

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
}

Notou a semelhança desse enum com a resposta de Sean Bright?

Eu acho que a coisa mais importante para mim é que ~é o mesmo operador em uma enumeração e em uma linha de código normal.

Mike
fonte
No seu segundo bloco de código, não deveriam &ser |s?
ClickRick
@ClickRick, obrigado pela captura. O segundo bloco de código realmente faz sentido agora.
Mike
1

A alternativa que eu pessoalmente uso, que faz a mesma coisa que a resposta de @Sean Bright, mas parece melhor para mim, é esta:

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

Observe como a natureza binária desses números, que são todas as potências de dois, faz a seguinte afirmação verdadeira: (a + b + c) == (a | b | c). E IMHO, +parece melhor.

Camilo Martin
fonte
1

Fiz algumas experiências com o ~ e achei que poderia ter armadilhas. Considere esse trecho para LINQPad, que mostra que o valor All enum não se comporta conforme o esperado quando todos os valores são ored 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
fonte
Norma não deve obter o valor 0, mas algo maior que 0.
Adrian Iftode
0

Só quero adicionar, se você usar [Flags] enum, pode ser mais conveniente usar o operador de deslocamento à esquerda bit a bit, assim:

[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
fonte
Isso não está respondendo à pergunta.
Servy