C #에서 플래그를 비교하는 방법?


155

아래에 깃발 열거 형이 있습니다.

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

어떻게하면 되나요?


내가 틀렸다면 정정하십시오 .0은 플래그 값으로 사용하기에 적합합니까?
Roy Lee

4
@Roylee : 0은 허용되며 플래그가 설정되어 있지 않은지 테스트하기 위해 "없음"또는 "정의되지 않음"플래그를 갖는 것이 좋습니다. 꼭 필요한 것은 아니지만 좋은 습관입니다. 이것에 대해 기억해야 할 중요한 것은 Leonid가 그의 대답에서 지적한 것입니다.
Andy

5
@Roylee 실제로 None0 에서는 플래그를 값으로 제공하는 것이 좋습니다 . msdn.microsoft.com/en-us/library/vstudio/…
ThatMatthew

많은 사람들이 또한 비트 비교가 그래서 그냥 collection.contains 플래그를 할 수있는 플래그의 수집, 찬성 피해야한다 읽기가 너무 어렵다 주장
MikeT

당신은 당신에게 논리를 반전 할 필요를 제외하고, 당신은 비트가 필요, 아주 가까웠다 &비교 연산자를 |첨가 같다 : 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0로 평가하고 false그 밖의 모든 것을 평가합니다 true.
Damian Vogel

답변:


321

.NET 4에는 새로운 메소드 Enum.HasFlag가 있습니다. 이것은 당신이 쓸 수 있습니다 :

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

훨씬 더 읽기 쉬운 IMO입니다.

.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
아, 마침내 상자 밖으로 무언가. 이 대단한 기능은 오랫동안 비교적 간단한 기능입니다. 다행이다.
Rob van Groenewoud

9
그러나이 방법의 성능 문제를 보여주는 아래 답변은 일부 사람들에게 문제가 될 수 있습니다. 행복하지 않습니다.
Andy Mortimer

2
이 메소드의 성능 고려 사항은 Enum클래스 의 인스턴스로 인수를 취하기 때문에 boxing 입니다.
Adam Houldsworth

1
성능 문제에 대한 자세한 내용은 다음 답변을 참조하십시오. stackoverflow.com/q/7368652/200443
Maxence

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

(testItem & FlagTest.Flag1) 비트 AND 연산입니다.

FlagTest.Flag1001OP의 열거 형과 같습니다 . 이제 testItemFlag1과 Flag2가 있다고 가정 해 봅시다 101.

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

2
여기의 논리는 정확히 무엇입니까? 술어를 왜 이렇게 작성해야합니까?
Ian R. 오브라이언

4
@ IanR.O'Brien Flag1 | 플래그 2는 011과 동일한 001 또는 010으로 변환됩니다. 이제 011 == Flag1 또는 011 == 001로 변환하면 항상 false를 반환합니다. 이제 Flag1을 사용하여 비트 AND를 수행하면 011 AND 001로 변환되어 001을 반환합니다. 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 문 ( flag1에 설정된 경우 true로 평가됨 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();
    }
}

HasFlags 확장 방법은 1 천만 회 이상 반복되는 표준 비트 구현의 27ms와 비교하여 무려 4793ms가 걸립니다.


5
과연. HasFlag의 구현을 살펴보면 두 피연산자 모두에서 "GetType ()"을 수행한다는 것을 알 수 있습니다. 그런 다음 "Enum.ToUInt64 (value.GetValue ());" 비트 단위 점검을 수행하기 전에 두 피연산자 모두에 대해
user276648

1
테스트를 여러 번 실행했으며 HasFlags의 경우 ~ 500ms, 비트 단위의 경우 ~ 32ms입니다. 여전히 비트 단위로 수십 배 빠르지 만 HasFlags는 테스트에서 수십 배 감소했습니다. (달린 2.5GHz의 코어 I3 및 .NET 4.5에서 테스트)
MarioVW

1
@MarioVW .NET 4에서 여러 번 실행되는 i7-3770은 AnyCPU (64 비트) 모드에서 ~ 2400ms ~ ~ 20ms, 32 비트 모드에서 ~ 3000ms ~ ~ 20ms를 제공합니다. .NET 4.5가 약간 최적화했을 수 있습니다. 또한 64 비트와 32 비트 빌드의 성능 차이는 64 비트 산술이 빠르기 때문에 발생할 수 있습니다 (첫 번째 주석 참조).
Bob

1
(«flag var» & «flag value») != 0나를 위해 작동하지 않습니다. 조건이 항상 실패하고 내 컴파일러 (Unity3D의 Mono 2.6.5) 가에 사용될 때 "경고 CS0162 : 도달수없는 코드가 감지되었습니다"라고 보고 합니다 if (…).
Slipp D. Thompson

1
@ wraith808 : 나는 당신이 내 테스트에서 올바른 것으로 잘못 판단하고 있음을 깨달았습니다 … = 1, … = 2, … = 4. enum 값 의 2의 힘은를 사용할 때 매우 중요 [Flags]합니다. 1Po2에서 자동으로 항목을 시작 하고 진행 한다고 가정했습니다 . 동작은 MS .NET과 Unity의 오래된 Mono에서 일관성있게 보입니다. 사과드립니다.
Slipp D. Thompson

21

확장 방법을 설정했습니다 : related question .

원래:

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

우연히 열거 형에 사용하는 규칙은 표준에 대해서는 단수, 플래그에는 복수입니다. 그렇게하면 열거 형 이름에서 여러 값을 보유 할 수 있는지 알 수 있습니다.


이것은 주석이어야하지만 새로운 사용자이므로 아직 주석을 추가 할 수없는 것처럼 보입니다 ... public static bool IsSet (this Enum input, Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } 어떤 종류의 열거 형과도 호환되는 방법이 있습니까 (여기서 열거 형이 UInt64 유형이거나 음수 값을 가질 수 있으면 작동하지 않기 때문에)?
user276648

Enum.HasFlag (Enum) (.net 4.0에서 사용 가능)와 중복됩니다
PPC

1
@ PPC 나는 중복을 정확하게 말하지 않을 것입니다. 많은 사람들이 이전 버전의 프레임 워크에서 개발하고 있습니다. 그러나 .Net 4 사용자는 HasFlag대신 확장을 사용해야합니다 .
Keith

4
@Keith : 또한 주목할만한 차이점이 있습니다 : ((FlagTest) 0x1) .HasFlag (0x0)는 true를 반환합니다. 원하는 동작
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는 ANYTHING & 0은 항상 == 0이므로 항상 참입니다.


대신 입력 값이 0인지 간단히 확인해야합니다.

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

방금 0 플래그 버그를 수정했습니다. 테스트하기 전에 0의 플래그 값을 알아야하기 때문에 .NET 프레임 워크 (3.5)의 디자인 오류라고 생각합니다.
thersch


5

비트 연산의 경우 비트 연산자를 사용해야합니다.

트릭을 수행해야합니다.

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

편집 : if if 수정-C / C ++ 방식으로 다시 빠져 들었습니다 (Ryan Farley가 지적 해 주셔서 감사합니다)


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 비트 세트를 그대로두고이 결과를 Flag1과 비교합니다.


1

[Flags] 없이도 이런 식으로 사용할 수 있습니다

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

또는 0 값 열거 형이있는 경우

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.