Что такое тильда (~) в определении enum?

149

Я всегда удивляюсь, что даже после использования C # все это время мне все еще удается находить вещи, о которых я не знал ...

Я попытался поискать в интернете это, но использование «~» в поиске не работает для меня так хорошо, и я тоже не нашел ничего в MSDN (не сказать, что его там нет)

Я недавно видел этот фрагмент кода, что означает тильда (~)?

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

Я был немного удивлен, увидев это, поэтому я попытался скомпилировать его, и это сработало ... но я до сих пор не знаю, что это значит / делает. Любая помощь??

Hugoware
источник
4
Это удивительное и элегантное решение, позволяющее плавно обновлять перечисление с течением времени. К сожалению, он конфликтует с CA-2217 и выдает
Джейсон Койн
сейчас 2020 год, и я обнаружил, что думаю о тех же словах, что и те, с которых вы начинаете свой пост. Рад слышать, что я не одинок.
funkymushroom

Ответы:

136

~ - унарный оператор дополнения - он переворачивает биты своего операнда.

~0 = 0xFFFFFFFF = -1

в дополнении к двум арифметическим, ~x == -x-1

Оператор ~ можно найти практически на любом языке, который позаимствовал синтаксис у C, включая Objective-C / C ++ / C # / Java / Javascript.

Джимми
источник
Это классно. Я не осознавал, что ты можешь сделать это в перечислении. Обязательно буду использовать в будущем
Орион Эдвардс
Так что это эквивалент All = Int32.MaxValue? Или UInt32.MaxValue?
Джоэл Мюллер
2
Все = (без знака int) -1 == UInt32.MaxValue. Int32.MaxValue не имеет отношения.
Джимми
4
@ Stevo3000: Int32.MinValue равно 0xF0000000, что не равно ~ 0 (на самом деле это ~ Int32.MaxValue)
Джимми
2
@Jimmy: Int32.MinValue - 0x80000000. Он имеет только один установленный бит (а не четыре, которые F даст вам).
ILMTitan
59

Я думаю, что:

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

Было бы немного яснее.

Шон Брайт
источник
10
Это определенно. Единственный хороший эффект унарного состоит в том, что, если кто-то добавляет к перечислению, все автоматически включает его. Тем не менее, выгода не перевешивает отсутствие ясности.
ctacke
12
Я не понимаю, как это более понятно. Это добавляет или избыточность или неоднозначность: означает ли «Все» «точно набор этих 3» или «все в этом перечислении»? Если я добавлю новое значение, я должен также добавить его ко всем? Если я вижу, что кто-то еще не добавил новое значение для All, это намеренно? ~ 0 явно.
Кен
16
Помимо личных предпочтений, если бы значение ~ 0 было столь же ясно для ОП, как и для вас с мной, он никогда бы не поставил этот вопрос. Я не уверен, что это говорит о ясности одного подхода по сравнению с другим.
Шон Брайт
9
Поскольку некоторые программисты, использующие C #, могут еще не знать, что означает <<, следует ли нам кодировать это тоже для большей ясности? Думаю, нет.
Курт Коллер
4
@ Пол, это как-то безумно. Давайте прекратим использовать; на английском, потому что многие люди не понимают его использования. Или все эти слова, которые они не знают. Ух ты, такой маленький набор операторов, и мы должны придумать наш код для людей, которые не знают, что такое биты и как ими манипулировать?
Курт Коллер
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
}

Из-за двух дополнений в C #, ~0 == -1число, где все биты равны 1 в двоичном представлении.

Йоханнес Шауб - Литб
источник
Похоже, они создают битовый флаг для способа оплаты: 000 = Нет; 001 = Наличные; 010 = Проверить; 100 = кредитная карта; 111 = Все
Дилли-О
Это не дополнение к двум, это инвертирует все биты и добавляет один, так что дополнение к двум по-прежнему равно 0. О ~ 0 просто инвертирует все биты или дополнение к одному.
Beardo
Нет, два дополнения просто инвертируют все биты.
конфигуратор
2
@configurator - это не правильно. Они дополняют простую инверсию битов.
Дейв Маркл
C # использует два дополнения для представления отрицательных значений. конечно ~ не два дополнения, а просто инвертирует все биты. я не уверен, откуда ты это взял, Бердо
Йоханнес Шауб - Lit
17

Лучше чем

All = Cash | Check | CreditCard

решение, потому что если вы добавите другой метод позже, скажите:

PayPal = 8 ,

Вы уже закончили с тильдой-Все, но должны изменить всю линию с другой. Таким образом, его менее подвержен ошибкам позже.

С уважением

blabla999
источник
Лучше, если вы также скажете, почему он менее подвержен ошибкам. Например: если вы сохраните значение в базе данных / двоичном файле, а затем добавите еще один флаг в перечисление, оно будет включено в «Все», что означает, что «Все» будет всегда означать все, а не только до тех пор, пока флаги одинаковые :).
Aidiakapi
11

Просто примечание, когда вы используете

All = Cash | Check | CreditCard

у вас есть дополнительное преимущество, Cash | Check | CreditCardкоторое оценивает Allи не оценивает другое значение (-1), которое не равно всем, в то время как содержит все значения. Например, если вы используете три флажка в пользовательском интерфейсе

[] Cash
[] Check
[] CreditCard

и суммировать их значения, и пользователь выбирает их все, что вы увидите Allв результирующем перечислении.

конфигуратор
источник
Вот почему вы используете myEnum.HasFlag()вместо: D
Pyritie
@Pyritie: Как ваш комментарий имеет отношение к тому, что я сказал?
конфигуратор
как в ... если бы вы использовали ~ 0 для "Все", вы могли бы сделать что-то вроде, All.HasFlag(Cash | Check | CreditCard)и это будет означать истину. Был бы обходной путь, так ==как не всегда работает с ~ 0.
Пирит
О, я говорил о том, что вы видите в отладчике, а ToStringне об использовании == All.
конфигуратор
9

Для тех, кто нашел этот вопрос осветительным, у меня есть небольшой ~пример, которым можно поделиться. Следующий фрагмент из реализации метода рисования, который подробно описан в этой документации Mono , использует ~с большим эффектом:

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

Без ~оператора код, вероятно, будет выглядеть примерно так:

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

... потому что перечисление выглядит так:

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
}

Заметили сходство этого перечисления с ответом Шона Брайта?

Я думаю, что самый важный вывод для меня - это ~тот же оператор в перечислении, что и в обычной строке кода.

Майк
источник
Во втором блоке кода не должно &быть |s?
ClickRick
@ClickRick, спасибо за улов. Второй блок кода действительно имеет смысл сейчас.
Майк
1

Лично я использую альтернативу, которая делает то же самое, что и ответ @Sean Bright, но для меня выглядит лучше:

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

Обратите внимание , как бинарная природа этих чисел, которые все силы два, делает следующее утверждение верно: (a + b + c) == (a | b | c). И ИМХО, +выглядит лучше.

Камило Мартин
источник
1

Я провел некоторые эксперименты с ~ и обнаружил, что у него могут быть подводные камни. Рассмотрим этот фрагмент для LINQPad, который показывает, что значение перечисления All не ведет себя должным образом, когда все значения объединены вместе.

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
источник
Стандарт не должен получать значение 0, а что-то большее, чем 0.
Адриан Ифтоде
0

Просто хочу добавить, что если вы используете перечисление [Flags], то может быть удобнее использовать побитовый оператор сдвига влево, например:

[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
источник
Это не отвечает на вопрос вообще.
Servy