Наиболее распространенные побитовые операции C # над перечислениями

201

На всю жизнь я не могу вспомнить, как устанавливать, удалять, переключать или тестировать немного в битовом поле. Либо я не уверен, либо я их перепутал, потому что они мне редко нужны. Так что было бы неплохо иметь "бит-шпаргалку".

Например:

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

или

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

Можете ли вы привести примеры всех других распространенных операций, предпочтительно в синтаксисе C # с использованием перечисления [Flags]?

steffenj
источник
5
Это есть ответ здесь
Грег Роджерс
7
Жаль, что ссылка не появляется в подсказках вопроса по этой теме.
Кори
10
Этот вопрос помечен для c / c ++, однако, поэтому кто-то, ищущий информацию о C #, вероятно, не будет искать там, даже если синтаксис выглядит одинаковым.
Адам Лассек
Я не знаю менее подробный способ сделать битовый тест
Энди Джонсон
2
@ Энди, в .NET 4 теперь есть API для битового теста.
Дрю Ноакс

Ответы:

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
Hugoware
источник
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
@ Джим, возможно. Это просто имя примера, как оно используется в исходном вопросе, поэтому вы можете изменить его в своем коде.
Дрю Ноакс
14
Я знаю! Но уродливые имена похожи на IE6 и, вероятно, никогда не исчезнут :(
Джим Шуберт
5
@JimSchubert, опять же, я просто воспроизвел имя типа из исходного вопроса, чтобы не перепутать проблему. В правилах именования перечислимых типов .NET указано, что все [Flags]перечисления должны иметь множественные имена, поэтому у имени возникают 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 не является типобезопасным, поскольку принимает любой тип аргумента значения перечисления, а не только данный тип перечисления.
  2. HasFlagнеоднозначно, проверяет ли оно, имеет ли значение все или любой из флагов, предоставленных аргументом значения перечисления. Это все кстати.
  3. HasFlag довольно медленный, так как требует бокса, который вызывает выделение ресурсов и, следовательно, сбор мусора.

Частично из-за ограниченной поддержки перечислений флагов в .NET я написал библиотеку OSS Enums.NET, которая решает каждую из этих проблем и значительно облегчает работу с перечислениями флагов.

Ниже приведены некоторые операции, которые он предоставляет вместе с эквивалентными реализациями, использующими только .NET Framework.

Объединить флаги

.СЕТЬ             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. Вы можете проверить мое предложение здесь .

TylerBrinkley
источник
7

Синтаксис C ++, предполагая, что бит 0 равен LSB, предполагая, что flags не имеет длинных знаков:

Проверьте, установлено ли:

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))
Petesh
источник
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 установлен, то он имеет значение true) Переключить назад (1 - 0 или 0 - 1):
flags = flags ^ 0x08;
Сбросить бит 4 в ноль:
flags = flags & 0xFFFFFF7F;

Nashirak
источник
2
-1 так как это даже не с перечислениями? Плюс, ручное кодирование значений хрупко ... Я бы по крайней мере написал ~0x08вместо 0xFFFFFFF7... (фактическая маска для 0x8)
Бен Мошер
1
Сначала я думал, что у Бена -1 был резкий, но использование "0xFFFFFF7F" делает этот пример особенно плохим.
ToolmakerSteve
2

Это было вдохновлено использованием Sets в качестве индексаторов в 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 является байтовым перечислением: this.flags & = ~ index;
amuliar
0

C ++ операции: & | ^ ~ (для и, или, xor и не побитовых операций). Также представляют интерес >> и <<, которые являются операциями битового сдвига.

Таким образом, чтобы проверить, установлен ли бит во флаге, вы должны использовать: if (flags & 8) // тестовый бит 4 установлен

workmad3
источник
8
Вопрос касается c #, а не c ++
Энди Джонсон
3
С другой стороны, C # использует одни и те же операторы: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve
3
В защиту @ workmad3 исходные теги имели C и C ++
pqsk