Найпоширеніші C # побітові операції над перерахунками


201

За все життя я не можу пригадати, як встановити, видалити, переключити або перевірити трохи на бітфілді. Або я не впевнений, або я їх змішую, бо мені рідко потрібні. Тож "бит-шпаргалка" було б непогано мати.

Наприклад:

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

або

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

Чи можете ви навести приклади всіх інших загальних операцій, бажано в синтаксисі C #, використовуючи перелік [Прапори]?


5
На це вже відповіли тут
Грег Роджерс

7
занадто погано, що посилання не відображається в підказках до цієї теми.
cori

10
Це питання позначене як c / c ++, тому хтось, хто шукає інформацію про C #, ймовірно, не шукатиме там, хоча синтаксис здається таким самим.
Адам Лассек

Мені невідомий менш дослівний спосіб зробити тест біт
Енді Джонсон,

2
@Andy, зараз існує API для бітового тестування в .NET 4.
Дрю Ноакс

Відповіді:


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
Я також вважаю це корисним - Будь-які ідеї, як я можу змінити його, щоб він працював на будь-якому типі?
Чарлі Сольс

7
Ці розширення просто зробили мій день, тиждень, місяць і, можливо, мій рік.
thaBadDawg

Дякую! Всі: не забудьте перевірити оновлення, до якого пов’язано Hugoware.
Хельге Кляйн

Дуже приємний набір розширень. Прикро, що вони вимагають боксу, хоча я не можу придумати альтернативу, яка не використовує бокс, і це є стислим. Навіть новий HasFlagметод Enumвимагає боксу.
Дрю Ноакс

4
@Drew: Дивіться code.google.com/p/unconstrained-melody, як уникнути боксу :)
Джон Скіт,

109

У .NET 4 тепер можна писати:

flags.HasFlag(FlagsEnum.Bit4)

4
+1, щоб вказати на це, хоча FlagsEnumце некрасиве ім'я. :)
Джим Шуберт

2
@Jim, можливо. Це лише зразок імені, як використовується в оригінальному запитанні, тому ви можете змінити його у своєму коді.
Дрю Ноакс

14
Я знаю! Але потворні імена схожі на IE6 і, ймовірно, ніколи не зникнуть :(
Джим Шуберт

5
@JimSchubert, знову ж таки, я просто відтворив назву типу з оригінального питання, щоб не плутати проблему. В Рекомендації Іменування .NET перераховується типу показує , що все [Flags]перерахування повинні мати pluralised імен, так що назва FlagsEnumмає ще більш серйозні проблеми , ніж неподобства.
Дрю Ноакс

1
Я також рекомендую Рамкові рекомендації щодо дизайну: Конвенції, ідіоми та зразки для багаторазових бібліотек .NET . Купувати це трохи дорого, але я вважаю, що Safari Online і Books24x7 обидва пропонують це для передплатників.
Джим Шуберт

89

Ідіома полягає в тому, щоб використовувати бітовий або рівний оператор для встановлення бітів:

flags |= 0x04;

Щоб трохи очистити, ідіома полягає у використанні побіжно та з запереченням:

flags &= ~0x04;

Іноді у вас є зміщення, яке ідентифікує ваш біт, і тоді ідіома полягає в тому, щоб використовувати їх у поєднанні з лівим зміщенням:

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

22

@Малював

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


10
Хоча, безумовно, цікаво і добре зазначити. Вам потрібно врахувати використання. Згідно з цим, якщо ви не виконуєте пару сотень тисяч і більше операцій, ви, мабуть, навіть не збираєтесь цього помічати.
Джошуа Хайес

7
HasFlagМетод включає в себе бокс / розпакування, на частку якого припадає цю різницю. Але вартість настільки тривіальна (0,4 мкс), що якщо ви не перебуваєте в тісному циклі, я б прийняв більш читаний (і менш вірогідний помилковий) декларативний дзвінок API будь-якого дня.
Дрю Ноакс

8
Залежно від використання це може бути проблемою. А оскільки я досить багато працюю з навантажувачами, я подумав, що це добре зазначити.
Чак Ді

11

На жаль, вбудовані операції перерахування прапора .NET, на жаль, досить обмежені. Більшість часу користувачі залишаються розбиратися в бітовій логіці роботи.

У .NET 4 HasFlagбув доданий метод, Enumякий допомагає спростити код користувача, але, на жаль, з ним існує багато проблем.

  1. HasFlag не є безпечним для типу, оскільки він приймає будь-який тип аргументу значення enum, а не лише заданий тип enum.
  2. HasFlagнеоднозначно, чи перевіряє, чи має значення цілий чи будь-який з прапорів, наданих аргументом значення enum. Це все до речі.
  3. HasFlag досить повільний, оскільки вимагає боксу, що спричиняє виділення і, таким чином, більше сміття.

Частково завдяки обмеженій підтримці .NET для переліків прапорців я написав бібліотеку OSS Enums.NET, яка вирішує кожну з цих проблем і полегшує справу з прапорцями.

Нижче наведено деякі операції, які він забезпечує разом з їх еквівалентними реалізаціями, використовуючи лише .NET-фреймворк.

Поєднайте прапори

.NET             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Видалити прапори

.NET             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Загальні прапори

.NET             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Увімкніть прапори

.NET             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Має всі прапори

.NET             (flags & otherFlags) == otherFlags абоflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


Має якісь прапори

.NET             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Отримати прапори

.NET

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

Синтаксис C ++, якщо припустимо, що біт 0 є LSB, якщо прапори довго не підписані:

Перевірте, чи встановлено:

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 до нуля:
flags = flags & 0xFFFFFF7F;


2
-1 оскільки це навіть не турбує перерахунків? Плюс ручне кодування значень є крихким ... Я б принаймні писав ~0x08замість 0xFFFFFFF7... (фактична маска для 0x8)
Бен Мошер

1
Спочатку я думав, що Бен -1 був суворим, але використання "0xFFFFFF7F" робить це особливо поганим прикладом.
ToolmakerSteve

2

На це надихнуло використання наборів як індексаторів у Delphi, коли:

/// 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 є байтом enum: this.flags & = ~ index;
amuliar

0

C ++ операціями є: & | ^ ~ (для і, або, xor, а не побітових операцій). Цікавими є також >> та <<, які є операціями з переміщенням у біт.

Отже, для тесту на наявність біта, встановленого у прапорі, ви використовуєте: if (flags & 8) // тест біт 4 встановлено


8
Питання стосується c #, а не c ++
Енді Джонсон

3
З іншого боку, C # використовує одні й ті ж оператори: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
На захист @ workmad3, оригінальні теги мали C та C ++
pqsk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.