Як порівняти прапори в C #?


155

У мене внизу є перетяжка прапора.

[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

Я не можу змусити твердження if оцінити як істинне.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

if (testItem == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Як я можу зробити це справжнім?


Виправте мене, якщо я помиляюся, чи доцільно використовувати 0 як значення прапора?
— Рой Лі

4
@Roylee: 0 прийнятно, і це гарна ідея мати прапор "None" або "Undefined", щоб перевірити, чи не встановлені прапори. Це ні в якому разі не потрібно, але це хороша практика. На це важливо пам’ятати, вказує Леонід у своїй відповіді.
— Енді

5
@Roylee Насправді Microsoft рекомендує надати Noneпрапор із значенням нуля. Дивіться msdn.microsoft.com/en-us/library/vstudio/…
— ThatMatthew

Багато людей також стверджують, що порівняння бітів занадто складно читати, тому його слід уникати на користь колекції прапорів, де можна просто зробити collection.contains flag
— MikeT

Ви були дуже близькі, за винятком того, що вам потрібно інвертувати вам логіку, вам потрібен побітовий &оператор для порівняння, |це як доповнення: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0оцінює до false, все інше до true.
— Даміан Фогель

Відповіді:


321

У .NET 4 з'явився новий метод Enum.HasFlag . Це дозволяє писати:

if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

що набагато читає, ІМО.

Джерело .NET вказує, що це виконує ту саму логіку, що і прийнята відповідь:

public Boolean HasFlag(Enum flag) {
    if (!this.GetType().IsEquivalentTo(flag.GetType())) {
        throw new ArgumentException(
            Environment.GetResourceString(
                "Argument_EnumTypeDoesNotMatch", 
                flag.GetType(), 
                this.GetType()));
    }

    ulong uFlag = ToUInt64(flag.GetValue()); 
    ulong uThis = ToUInt64(GetValue());
    // test predicate
    return ((uThis & uFlag) == uFlag); 
}

23
Ага, нарешті щось з коробки. Це чудово, я дуже довго чекав цієї відносно простої функції. Радий, що вони вирішили засунути його.
— Роб ван Гроенвоуд

9
Однак зауважте, що відповідь нижче показує проблеми з ефективністю цього методу - це може бути проблемою для деяких людей. На щастя не для мене.
— Енді Мортімер

2
Розгляд продуктивності цього методу є бокс, оскільки він бере аргументи як екземпляр Enumкласу.
— Адам Холдсворт

1
Для отримання інформації про питання ефективності дивіться цю відповідь: stackoverflow.com/q/7368652/200443
— Maxence

180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) - це побіт І операція.

FlagTest.Flag1еквівалентно перерахунку 001ОП. Тепер скажімо, testItemє Flag1 і Flag2 (так це побіжно 101):

  001
 &101
 ----
  001 == FlagTest.Flag1

2
Яка саме тут логіка? Чому присудок повинен писатися так?
— Ian R. O'Brien

4
@ IanR.O'Brien Flag1 | Прапор 2 перекладається на 001 або 010, що є тим же самим, що й 011, тепер якщо ви дорівнює рівності 011 == Flag1 або перекладено 011 == 001, це завжди повертає помилку. Тепер, якщо ви зробите побіжно AND with Flag1, то це перекладається на 011 AND 001, який повертає 001, а тепер рівність повертає true, тому що 001 == 001
— pqsk

Це найкраще рішення, оскільки HasFlags значно більш ресурсомісткий. І більше того, чим займається HasFlags, тут робиться компілятор
— Sebastian Xawery Wiśniowiecki

78

Для тих, хто має проблеми із уявленням про те, що відбувається з прийнятим рішенням (що це таке),

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (відповідно до питання) визначається як,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Тоді у висловленні if ліва частина порівняння -

(testItem & flag1) 
 = (011 & 001) 
 = 001

І повне твердження if (яке оцінюється як true, якщо flag1воно встановлено testItem),

(testItem & flag1) == flag1
 = (001) == 001
 = true

25

@ phil-devaney

Зауважте, що, за винятком найпростіших випадків, Enum.HasFlag несе велику штрафну ефективність порівняно з написанням коду вручну. Розглянемо наступний код:

[Flags]
public enum TestFlags
{
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256,
    Ten = 512
}


class Program
{
    static void Main(string[] args)
    {
        TestFlags f = TestFlags.Five; /* or any other enum */
        bool result = false;

        Stopwatch s = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; i++)
        {
            result |= f.HasFlag(TestFlags.Three);
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*

        s.Restart();
        for (int i = 0; i < 10000000; i++)
        {
            result |= (f & TestFlags.Three) != 0;
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*        

        Console.ReadLine();
    }
}

Більше 10 мільйонів ітерацій метод розширення HasFlags займає колосальні 4793 мс порівняно з 27 мс для стандартної побітової реалізації.


5
Справді. Якщо ви подивитеся на реалізацію HasFlag, ви побачите, що вона робить "GetType ()" на обох операндах, що досить повільно. Тоді він робить "Enum.ToUInt64 (value.GetValue ());" на обох операндах, перш ніж робити побітну перевірку.
— користувач276648

1
Я кілька разів провів ваш тест і отримав ~ 500 мс для HasFlags і ~ 32 мс за біт. Незважаючи на те, що на порядок швидше з бітовим набором, HasFlags був на порядок нижче від вашого тесту. (Пройшов тест на 2.5GHz Core i3 та .NET 4.5)
— MarioVW

1
@MarioVW Запуск декількох разів у .NET 4, i7-3770 дає ~ 2400 мс ~ 20 мс в режимі AnyCPU (64-бітний), а ~ 3000 мс ~ ~ 20 мс у 32-бітному режимі. .NET 4.5, можливо, трохи оптимізував це. Також зверніть увагу на різницю продуктивності між 64-бітною та 32-розрядною збірками, що може бути пов'язано з швидшою арифметикою 64-бітової версії (див. Перший коментар).
— Боб

1
(«flag var» & «flag value») != 0не працює для мене. Ця умова завжди провалюється, і мій компілятор (Unity3D's Mono 2.6.5) повідомляє про "попередження CS0162: Недоступний код виявлений" при використанні в if (…).
— Сліпп Д. Томпсон

1
@ wraith808: Я зрозумів, що при моєму тесті помилка робила те, що ти маєш правильність у своїх силах - потужність 2 … = 1, … = 2, … = 4на значення перерахунків критично важлива при використанні [Flags]. Я припускав, що він розпочне записи 1і просуне Po2s автоматично. Поведінка виглядає несуперечливою у даній монографії MS .NET та Unity. Мої вибачення, що додали це вам.
— Сліпп Д. Томпсон

21

Я створив метод розширення, щоб це зробити: пов'язане питання .

В основному:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

Тоді ви можете зробити:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

if( testItem.IsSet ( FlagTests.Flag1 ) )
    //Flag1 is set

Між іншим, умова, яку я використовую для переліків, є одниною для стандартного, множини для прапорів. Таким чином ви знаєте з імені enum, чи може він містити кілька значень.


Це має бути коментарем, але, оскільки я новий користувач, схоже, що я просто не можу додавати коментарі ... публічний статичний bool IsSet (це введення Enum, Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } Чи є спосіб бути сумісним з будь-яким типом перерахунків (адже тут він не працюватиме, якщо ваш перелік має тип UInt64 або може мати негативні значення)?
— користувач276648

Це дуже непотрібно для Enum.HasFlag (Enum) (доступно в .net 4.0)
— PPC

1
@PPC Я б не сказав точно зайвим - багато людей розробляють старіші версії фреймворку. Ви праві, хоча .Net 4 користувачі повинні використовувати HasFlagрозширення замість цього.
— Кіт

4
@Keith: Також є помітна різниця: ((FlagTest) 0x1) .HasFlag (0x0) повернеться правдою, що може бути або не бути розшукуваною поведінкою
— PPC

19

Ще одна порада ... Ніколи не робіть стандартну бінарну перевірку з прапором, значення якого "0". Ваш чек на цей прапор завжди буде вірним.

[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value = "FullInfo")]
    FullInfo=0,
    [EnumMember(Value = "BusinessData")]
    BusinessData=1
}

Якщо вхідний параметр бінарної перевірки проти FullInfo - ви отримаєте:

detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;

bPRez завжди буде істинним як будь-що & 0 завжди == 0.


Натомість слід просто перевірити, що значення вводу дорівнює 0:

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);

Я щойно виправив таку помилку 0-прапор. Я думаю, що це помилка дизайну в .NET Framework (3.5), оскільки вам потрібно знати, яке з значень прапора дорівнює 0, перш ніж тестувати його.
— thersch


5

Для бітових операцій потрібно використовувати побітові оператори.

Для цього слід зробити фокус:

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Редагувати: виправлено мою перевірку - я просунувся назад до моїх C / C ++ способів (дякую Райану Фарлі за вказівку)


5

Щодо редагування. Ви не можете зробити це правдою. Я пропоную вам перенести те, що ви хочете, в інший клас (або метод розширення), щоб наблизитися до потрібного вам синтаксису.

тобто

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}

4

Спробуйте це:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
В основному, ваш код запитує, чи встановити обидва прапори, те саме, що встановити один прапор, що, очевидно, неправдиво. У наведеному вище коді залишиться лише встановлений біт Flag1, якщо він взагалі встановлений, а потім порівнює цей результат з Flag1.


1

навіть без [Прапорів] ви можете використовувати щось подібне

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

або якщо у вас є нульове значення перерахунку

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.