Як порівняти прапори в 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.