Що таке тильда (~) у визначенні enum?


149

Мене завжди дивує, що навіть після використання C # за весь цей час мені все одно вдається знайти речі, про які я не знав ...

Я намагався шукати в Інтернеті це, але використання "~" у пошуку не працює для мене так добре, і я теж не знайшов нічого в MSDN (не кажучи, що його немає)

Нещодавно я побачив цей фрагмент коду, що означає 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
}

Я трохи здивувався, побачивши це, тому спробував скласти його, і це спрацювало ... але я досі не знаю, що це означає / робить. Будь-яка допомога ??


4
Це дивовижне та елегантне рішення, що дозволяє плавно модернізувати перелік у часі. На жаль, це конфлікт із CA-2217 і призведе до помилки, якщо ви використовуєте аналіз коду :( msdn.microsoft.com/en-us/library/ms182335.aspx
Jason Coyne

зараз це 2020 рік, і я вважаю, що думаю ті ж слова, що і ті, з якими ви починаєте свою посаду. Радий почути, що я не один.
funkymushroom

Відповіді:


136

~ - оператор доповнення одинарного - він перегортає біти свого операнда.

~0 = 0xFFFFFFFF = -1

в арифметиці доповнення двох, ~x == -x-1

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


Круто. Я не здогадувався, що ти можеш це зробити зануритися. Однозначно використовуватимуться у майбутньому
Orion Edwards

Отже, це еквівалент 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 "або" все в цьому переліку "? Якщо я додаю нове значення, чи слід також додати його до всіх? Якщо я бачу, що хтось ще не додав нової цінності для всіх, це навмисно? ~ 0 явний.
Кен

16
Особисті переваги осторонь, якби значення ~ 0 було таким ясним для ОП, як і вам і мені, він ніколи б не ставив це питання в першу чергу. Я не впевнений, що це говорить про чіткість одного підходу проти іншого.
Шон Яскравий

9
Оскільки деякі програмісти, які використовують C #, ще не знають, що означає «<<», чи слід також кодувати це для більшої ясності? Я думаю, що не.
Курт Коллер

4
@Paul, це неначе божевільний. Давайте перестанемо використовувати; англійською, оскільки багато людей не розуміють його використання. Або всі ті слова, яких вони не знають. Вау, такий невеликий набір операторів, і ми повинні придумати свій код для людей, які не знають, що таке біти і як ними маніпулювати?
Курт Коллер

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. ~ 0 просто перевертає всі біти чи доповнення.
Беардо

Ні, доповнення двох - це просто інвертування всіх біт.
конфігуратор

2
@configurator - це не правильно. Доповнення до них - це проста інверсія бітів.
Дейв Маркл

c # використовує два доповнення для представлення негативних значень. звичайно ~ це не два доповнення, а просто перевертає всі біти. я не впевнений, звідки ти це взяв, Беардо
Йоганнес Шауб - ліб

17

Це краще, ніж

All = Cash | Check | CreditCard

рішення, оскільки якщо ви додасте інший метод пізніше, скажіть:

PayPal = 8 ,

ви вже будете робити з тильдом-Усі, але доведеться змінити суцільну лінію на іншу. Тож його менш схильні до помилок пізніше.

з повагою


Краще, якщо ви також скажете, чому він менш схильний до помилок. Як би: якщо ви зберігаєте значення у базі даних / двійкового файлу, а потім додаєте інший прапор до перерахунку, воно буде включене у поле "Усі", що означає, що "Усі" завжди означатиме все, а не тільки, доки прапори однакові :).
Айдіакапі

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

Для інших, хто визначив це питання висвітлюючим, я маю короткий ~приклад, щоб поділитися. Наступний фрагмент з реалізації способу фарби, детально описаний у цій монодокументації , має ~великий ефект:

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 enum не веде себе так, як очікувалося, коли всі значення порушені разом.

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 
}

Стандарт не повинен отримувати значення 0, але щось більше, ніж 0.
Адріан Іфтоде

0

Просто хочу додати, якщо ви використовуєте [Flags] enum, то, можливо, буде зручніше використовувати бітовий лівий оператор зсуву, як це:

[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
}

Це зовсім не відповідає на питання.
Сервіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.