Как сравнить флаги в C#?

у меня есть перечисление флага ниже.

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

Я не могу сделать оператор if равным true.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

Как я могу это сделать?

11 ответов


в .NET 4 есть новый метод перечисление.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); 
}

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

(testItem & FlagTest.Flag1) является побитовым и операция.

FlagTest.Flag1 эквивалентно 001 с перечислением OP. Теперь скажем testItem имеет Flag1 и Flag2 (так что это побитовое 101):

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

для тех, у кого есть проблемы с визуализацией того, что происходит с принятым решением (вот это),

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

testItem (в соответствии с вопросом) определяется как,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

тогда в операторе if левая сторона сравнения:

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

и полный оператор if (который оценивает значение true if flag1 находится в testItem),

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

@phil-devaney

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


Я создал метод расширения, чтобы сделать это: вопрос.

по сути:

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

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


еще один совет... Никогда не делайте стандартную двоичную проверку с флагом, значение которого "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);

if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}

попробуйте это:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
в принципе, ваш код спрашивает, если оба флага установлены так же, как один флаг установлен, что, очевидно, ложно. Код выше оставит только набор битов Flag1, если он установлен вообще, а затем сравнивает этот результат с Flag1.

для битовых операций, необходимо использовать побитовые операторы.

это должно сделать трюк:

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

Edit: исправлена моя проверка if-я вернулся к своим способам C / C++ (спасибо Райану Фарли за указание)


в отношении изменить. Ты не можешь сделать это правдой. Я предлагаю вам обернуть то, что вы хотите в другой класс (или метод расширения), чтобы приблизиться к синтаксису нужно.

то есть

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

даже без [флагов] вы можете использовать что-то вроде этого

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

или если у вас есть нулевое значение enum

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