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

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

147 голосов
/ 02 сентября 2008

У меня есть флаг enum ниже.

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

308 голосов
/ 20 ноября 2009

В .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); 
}
175 голосов
/ 02 сентября 2008
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

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

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

  001
 &101
 ----
  001 == FlagTest.Flag1
78 голосов
/ 20 ноября 2009

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

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
24 голосов
/ 23 августа 2011

@ фил-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 мс для стандартной побитовой реализации.

21 голосов
/ 02 сентября 2008

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

В основном:

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

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

19 голосов
/ 05 августа 2010

Еще один совет ... Никогда не делайте стандартную двоичную проверку с флагом, значение которого равно "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);
7 голосов
/ 02 сентября 2008
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}
5 голосов
/ 02 сентября 2008

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

т.е.

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}
5 голосов
/ 02 сентября 2008

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

Это должно сработать:

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

Редактировать: Исправлена ​​моя проверка, если я - я вернулся к моим C / C ++ пути (спасибо Райан Фарли за указание на это)

4 голосов
/ 02 сентября 2008

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


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