C #에서 비트 마스크 사용


98

다음이 있다고 가정 해 봅시다.

int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000

메서드에 매개 변수로 10 (8 + 2)을 전달하고이를 수잔과 카렌을 의미하도록 디코딩하고 싶습니다.

10이 1010이라는 걸 알아

하지만 특정 비트가에서와 같이 확인되었는지 확인하는 논리를 어떻게 할 수 있습니까?

if (condition_for_karen) // How to quickly check whether effective karen bit is 1

지금 제가 생각할 수있는 것은 제가 전달한 숫자가

14 // 1110
12 // 1100
10 // 1010
8 //  1000

실제 시나리오에서 실제 비트 수가 많을 때 이것은 비현실적인 것 같습니다. 마스크를 사용하여 카렌의 조건을 충족하는지 여부를 확인하는 더 좋은 방법은 무엇입니까?

왼쪽으로 이동 한 다음 다시 오른쪽으로 이동 한 다음 다시 내가 관심있는 비트가 아닌 다른 비트로 이동하는 것을 생각할 수 있지만 이것은 지나치게 복잡해 보입니다.


7
사용법에 대해 언급해야했습니다. 비트 연산을 수행하는 경우 비트 조작 연산자 만 사용해야합니다. 즉, (8 + 2)가 아니라 (8 | 2)로 생각하십시오.
Jeff Mercado

Karen은 또한 귀하의 관리자에게 즉시 이야기하고 싶습니다.
Krythic

답변:


198

이를 수행하는 전통적인 방법은에 Flags속성 을 사용하는 것입니다 enum.

[Flags]
public enum Names
{
    None = 0,
    Susan = 1,
    Bob = 2,
    Karen = 4
}

그런 다음 다음과 같이 특정 이름을 확인합니다.

Names names = Names.Susan | Names.Bob;

// evaluates to true
bool susanIsIncluded = (names & Names.Susan) != Names.None;

// evaluates to false
bool karenIsIncluded = (names & Names.Karen) != Names.None;

논리적 인 비트 조합은 기억하기 어려울 수 있으므로 FlagsHelper클래스 *를 사용하여 자신의 삶을 더 쉽게 만듭니다 .

// The casts to object in the below code are an unfortunate necessity due to
// C#'s restriction against a where T : Enum constraint. (There are ways around
// this, but they're outside the scope of this simple illustration.)
public static class FlagsHelper
{
    public static bool IsSet<T>(T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        return (flagsValue & flagValue) != 0;
    }

    public static void Set<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue | flagValue);
    }

    public static void Unset<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue & (~flagValue));
    }
}

이렇게하면 위 코드를 다음과 같이 다시 작성할 수 있습니다.

Names names = Names.Susan | Names.Bob;

bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);

bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);

Karen다음을 수행하여 세트에 추가 할 수도 있습니다 .

FlagsHelper.Set(ref names, Names.Karen);

Susan비슷한 방법으로 제거 할 수 있습니다 .

FlagsHelper.Unset(ref names, Names.Susan);

* Forges가 지적했듯이 IsSet위 의 방법 과 동일한 방법이 .NET 4.0에 이미 존재합니다 Enum.HasFlag. 그러나 SetUnset메서드에는 동등한 항목이없는 것 같습니다. 그래서 저는이 수업에 약간의 장점이 있다고 말하고 싶습니다.


참고 : 열거 형을 사용하는 것은 이 문제를 해결 하는 일반적인 방법 일뿐 입니다. 위의 모든 코드를 완전히 번역하여 대신 int를 사용할 수 있으며 잘 작동합니다.


14
실제로 작동하는 첫 번째 코드는 +1입니다. 을 (names & Names.Susan) == Names.Susan필요로하지 않는 None.
Matthew Flaschen

1
@Matthew : 오 예, 좋은 지적입니다. 나는 None그것이 많은 시나리오에서 편리하다는 것을 알기 때문에 항상 모든 열거 형에 대한 값을 정의하는 습관을 가지고 있다고 생각합니다.
Dan Tao

31
이것은 당신이 당신의 도우미 방법이 필요하지 않습니다에 내장되어 있습니다 ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@Porges : 와우, 내가 그것을 어떻게 놓쳤는 지 모르겠습니다 ... 지적 해 주셔서 감사합니다! (하지만 .NET 4.0에서만 사용할 수있는 것처럼 보이지만 ... 또한 Set메서드에 해당하는 것은 없습니다 . 따라서 도우미 메서드가 적어도 완전히 가치가 없다고 말하고 싶습니다 .)
Dan Tao

6
using names.HasFlag(Names.Susan)(names & Names.Susan) == Names.Susan항상 같지는 않습니다 (names & Names.Susan) != Names.None. 예를 들어, 당신은 있는지 확인합니다 경우 names.HasFlag(Names.none)또는names.HasFlag(Names.Susan|Names.Karen)
ABCade

20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

비트 'and'는 Karen을 "나타내는"비트를 제외한 모든 것을 마스킹합니다. 각 사람이 단일 비트 위치로 표시되는 한 간단한 방법으로 여러 사람을 확인할 수 있습니다.

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}

12

여기에는 마스크를 데이터베이스 열에 int로 저장하는 방법과 나중에 마스크를 복원하는 방법을 보여주는 예제가 포함되어 있습니다.

public enum DaysBitMask { Mon=0, Tues=1, Wed=2, Thu = 4, Fri = 8, Sat = 16, Sun = 32 }


DaysBitMask mask = DaysBitMask.Sat | DaysBitMask.Thu;
bool test;
if ((mask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((mask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((mask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

// Store the value
int storedVal = (int)mask;

// Reinstate the mask and re-test
DaysBitMask reHydratedMask = (DaysBitMask)storedVal;

if ((reHydratedMask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((reHydratedMask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((reHydratedMask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

비슷한 작업을 수행했지만 마스크를 정의 할 때 Mon = Math.Power (2, 0), Tues = Math.Pow (2, 1), Wed = Math.Pow (2, 2) 등을했습니다. 그래서 비트 위치 이진수에서 십진수로 변환하는 데 익숙하지 않은 사용자에게는 좀 더 분명합니다. Blindy는 마스크 된 비트를 이동하여 부울 결과로 전환되기 때문에 좋습니다.
아날로그 방화범

7

비트 마스크를 결합하려면 bitwise- 또는 . 결합하는 모든 값에 정확히 1 비트가있는 사소한 경우 (예 : 예)는 값을 추가하는 것과 동일합니다. 그러나 겹치는 비트가 있거나 'ing'은 케이스를 우아하게 처리합니다.

다음 같이 마스크를 사용하여 비트 마스크 값 을 디코딩하려면 :

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();

1
C #에서는 정수를 부울로 사용할 수 없습니다.
Shadow

7

쉬운 방법:

[Flags]
public enum MyFlags {
    None = 0,
    Susan = 1,
    Alice = 2,
    Bob = 4,
    Eve = 8
}

플래그를 설정하려면 논리 "or"연산자를 사용하십시오 |.

MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;

플래그가 포함되어 있는지 확인하려면 HasFlag다음을 사용하십시오 .

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}

이 모든 정보가 이미 위에 제공된 것 같습니다. 새로운 정보를 제공하는 경우 명확하게 표시해야합니다.
sonyisda1

1
HasFlag()및 사용의 간단한 예는 [Flags]다른 답변에 제공되지 않았습니다.
A-Sharabiani

0

비트 마스크 대 개별 부울을 사용하는 또 다른 좋은 이유는 웹 개발자로서 한 웹 사이트를 다른 웹 사이트에 통합 할 때 자주 쿼리 문자열에 매개 변수 나 플래그를 보내야한다는 것입니다. 모든 플래그가 바이너리이면 여러 값을 bool로 보내는 것보다 단일 값을 비트 마스크로 사용하는 것이 훨씬 간단합니다. 데이터를 보내는 다른 방법 (GET, POST 등)이 있다는 것을 알고 있지만 쿼리 문자열의 간단한 매개 변수는 대부분 민감하지 않은 항목에 충분합니다. 외부 사이트와 통신하기 위해 쿼리 문자열에 128 개의 bool 값을 보내십시오. 이것은 또한 브라우저의 URL 쿼리 문자열에 제한을 적용하지 않는 추가 기능을 제공합니다.


OP의 질문에 대한 답변이 아닙니다. 주석이어야합니다.
sonyisda1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.