열거 형에서 가장 일반적인 C # 비트 연산


201

내 인생에서 비트 필드에서 비트를 설정, 삭제, 토글 또는 테스트하는 방법을 기억할 수 없습니다. 확실하지 않거나 거의 필요하지 않기 때문에 혼합합니다. 따라서 "비트 치트 시트"가 있으면 좋을 것입니다.

예를 들면 다음과 같습니다.

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

또는

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

[Flags] 열거 형을 사용하여 C # 구문에서 다른 모든 일반적인 작업의 예를 제공 할 수 있습니까?


5
이것은 이전에 대한 답이 여기에
그렉 로저스

7
이 주제에 대한 질문 힌트에 해당 링크가 나타나지 않습니다.
cori

10
이 질문은 c / c ++로 태그가 지정되어 있으므로 C #에 대한 정보를 검색하는 사람은 구문이 동일하더라도 보이지 않을 것입니다.
Adam Lassek

나는 비트 테스트를하는 덜 장황한 방법을 모른다
Andy Johnson

2
@Andy, .NET 4에는 비트 테스트를위한 API가 있습니다.
Drew Noakes

답변:


288

이 확장에 대해 더 많은 작업을 수행했습니다. 여기에서 코드를 찾을 수 있습니다.

나는 자주 사용하는 System.Enum을 확장하는 확장 방법을 썼습니다 ... 방탄이라고 주장하지는 않지만 도움이되었습니다 ... 댓글이 삭제되었습니다 ...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

그런 다음 다음과 같이 사용됩니다

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false

1
나는 또한 이것이 유용하다는 것을 발견했다-어떤 기본 유형에서 작동하도록 수정하는 방법이 있습니까?
Charlie Salts

7
이 확장은 단지 내 하루, 내 주, 내 달, 그리고 아마도 내 년을 만들었습니다.
thaBadDawg

감사합니다! 휴고웨어가 링크 한 업데이트를 확인하십시오.
Helge Klein

아주 좋은 확장 세트. 권투를 사용하지 않는 대안을 생각할 수는 없지만 간결한 것은 복싱이 필요하다는 것은 부끄러운 일입니다. 새로운 HasFlag방법 조차도 Enum권투 가 필요합니다.
Drew Noakes

4
@Drew : 권투를 피하는 방법은 code.google.com/p/unconstrained-melody 를 참조하십시오 :)
Jon Skeet

109

.NET 4에서는 다음과 같이 작성할 수 있습니다.

flags.HasFlag(FlagsEnum.Bit4)

4
FlagsEnum추악한 이름 이지만, 그것을 지적하기 위해 +1 . :)
Jim Schubert

2
@Jim 일 것입니다. 원래 질문에 사용 된 샘플 이름 일 뿐이므로 코드에서 자유롭게 변경할 수 있습니다.
Drew Noakes

14
알아! 그러나 추악한 이름은 IE6와 같으며 결코 사라지지 않을 것입니다 :(
Jim Schubert

5
@JimSchubert는 다시 문제를 혼동하지 않도록 원래 질문에서 유형 이름을 재현했습니다. .NET 열거 형 이름 지정 가이드 라인은 모든 것을 나타냅니다 [Flags]이름 때문에 열거가, pluralised 이름을 가져야한다 FlagsEnum추함보다 더 심각한 문제가 있습니다.
Drew Noakes

1
또한 프레임 워크 디자인 지침 : 재사용 가능한 .NET 라이브러리의 규칙, 숙어 및 패턴을 권장 합니다. 구입하는 데 약간 비싸지 만 Safari Online과 Books24x7은 모두 가입자에게 제공한다고 생각합니다.
Jim Schubert

89

관용구는 비트 또는 같음 연산자를 사용하여 비트를 설정하는 것입니다.

flags |= 0x04;

비트를 정리하기 위해 관용구는 비트 단위와 부정을 사용하는 것입니다.

flags &= ~0x04;

때때로 당신은 당신의 비트를 식별하는 오프셋을 가지고 있고, 관용구는 이것을 왼쪽 시프트와 결합하여 사용하는 것입니다 :

flags |= 1 << offset;
flags &= ~(1 << offset);

22

D

가장 간단한 경우를 제외하고 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가 걸립니다.


10
확실히 흥미롭고 지적하는 것이 좋습니다. 사용법을 고려해야합니다. 이것에 따르면, 당신이 몇 십만 이상의 작전을 수행하지 않는다면 아마 이것을 눈치 채지 못할 것입니다.
Joshua Hayes

7
HasFlag방법은이 차이를 설명하는 boxing / unboxing을 포함합니다. 그러나 비용은 너무 사소 (0.4µs)이므로 루프가 빠르지 않으면 매일 더 읽기 쉽고 버그가 적은 선언적 API 호출을 사용합니다.
Drew Noakes

8
사용법에 따라 문제가 될 수 있습니다. 로더를 다루는 작업이 많았 기 때문에 지적하는 것이 좋았다고 생각했습니다.
척 디

11

.NET의 내장 플래그 열거 연산은 불행히도 상당히 제한적입니다. 대부분의 경우 사용자는 비트 연산 논리를 알아 내야합니다.

.NET 4에는 사용자의 코드를 단순화하는 데 도움 HasFlagEnum되는 방법 이 추가 되었지만 불행히도 많은 문제가 있습니다.

  1. HasFlag 주어진 열거 형뿐만 아니라 모든 유형의 열거 형 값 인수를 허용하므로 형식이 안전하지 않습니다.
  2. HasFlag값에 열거 형 값 인수가 제공하는 플래그가 모두 있는지 또는 없는지 여부를 확인하는 것이 모호합니다. 그건 그렇고
  3. HasFlag 복싱이 필요하기 때문에 할당이 느려져 가비지 수집이 증가합니다.

플래그 열거 형에 대한 .NET의 제한된 지원으로 인해 이러한 각 문제를 해결하고 플래그 열거 형을 훨씬 쉽게 처리 할 수 있는 OSS 라이브러리 Enums.NET 을 작성했습니다 .

다음은 .NET 프레임 워크를 사용하는 동등한 구현과 함께 제공되는 일부 조작입니다.

플래그 결합

.그물             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


깃발 제거

.그물             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


공통 깃발

.그물             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


토글 플래그

.그물             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


모든 플래그가 있음

.NET             (flags & otherFlags) == otherFlags 또는flags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


플래그가 있습니다

.그물             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


깃발 얻기

.그물

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


이러한 개선 사항을 .NET Core에 통합하고 궁극적으로 전체 .NET Framework를 얻으려고합니다. 내 제안을 여기서 확인할 수 있습니다 .


7

비트 0이 LSB라고 가정하고 C ++ 구문은 플래그가 부호가 없다고 가정합니다.

설정되어 있는지 확인하십시오.

flags & (1UL << (bit to test# - 1))

설정되어 있지 않은지 확인하십시오.

invert test !(flag & (...))

세트:

flag |= (1UL << (bit to set# - 1))

명확한:

flag &= ~(1UL << (bit to clear# - 1))

비녀장:

flag ^= (1UL << (bit to set# - 1))

3

최상의 성능과 쓰레기를 없애려면 다음을 사용하십시오.

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}

2

비트를 테스트하려면 다음을 수행하십시오. (플래그가 32 비트 숫자라고 가정)

테스트 비트 :

if((flags & 0x08) == 0x08)
(비트 4가 설정되면 참) 토글 백 (1-0 또는 0-1) :
flags = flags ^ 0x08;
비트 4를 0으로 재설정 :
flags = flags & 0xFFFFFF7F;


2
열거 형을 귀찮게하지 않기 때문에 -1입니까? 또한, 값을 수동으로 코딩하는 것은 깨지기 쉽다 ... 나는 적어도 ... (0x8을위한 실제 마스크) ~0x08대신 쓸 것이다0xFFFFFFF7
Ben Mosher

1
처음에는 Ben의 -1이 거칠다고 생각했지만 "0xFFFFFF7F"를 사용하면 특히 나쁜 예가됩니다.
ToolmakerSteve

2

델파이에서는 다음과 같은 경우에 색인으로 색인기를 사용하여 영감을 얻었습니다.

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}

2
BindingFlags가 바이트 열거 형인 경우에도 컴파일되지 않습니다. this.flags & = ~ index;
amuliar

0

C ++ 작업은 다음과 같습니다. & | ^ ~ (비트 연산이 아닌 xor 및 xor). 비트 시프트 연산 인 >> 및 <<도 관심이 있습니다.

따라서 플래그에 설정된 비트를 테스트하려면 다음을 사용하십시오. if (flags & 8) // 비트 4가 설정되었는지 테스트


8
C ++이 아닌 C #과 관련된 질문
Andy Johnson

3
반면에 C #은 동일한 연산자를 사용합니다. msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
@ workmad3를 방어하면서, 원래의 태그는 C와 C ++를 가지고있었습니다
pqsk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.