Тестирование поразрядных значений Enum - PullRequest
17 голосов
/ 24 мая 2011

Раньше я действительно не использовал побитовые перечисления, и я просто хочу убедиться, что мои тесты верны.Меня больше всего интересует тестирование значений None и All.Мы получаем данные от веб-службы, которая использует это перечисление для классификации определенных фрагментов данных.Учитывая это, я предполагаю, что ни None, ни All никогда не будут объединены с какой-либо другой ценностью.

Учитывая следующее побитовое определение перечисления;

[System.FlagsAttribute()]
public enum TrainingComponentTypes : int
    {
        None = 0,
        AccreditedCourse = 1,
        Qualification = 2,
        Unit = 4,
        SkillSet = 8,
        UnitContextualisation = 16,
        TrainingPackage = 32,
        AccreditedCourseModule = 64,
        All = 127,
    }

Я прочитал следующую цитату на этом сайте MSDN о FlagAttributes;

ИспользованиеNone как имя константы перечисляемого флага, значение которой равно нулю.Вы не можете использовать перечисляемую константу None в побитовой операции AND для проверки флага, потому что результат всегда равен нулю.Однако вы можете выполнить логическое, а не побитовое сравнение между числовым значением и константой None для перечисления, чтобы определить, установлены ли какие-либо биты в числовом значении.

Выполняет ли логическое сравнение в этом случае ссылаются на нормальный тест на равенство для перечислений?Например,

TrainingComponentTypes tct = TrainingComponentTypes.None; 
if (tct == TrainingComponentTypes.None) 
{ ... }

Для побитового сравнения я выполняю следующее:

 TrainingComponentTypes tct = TrainingComponentTypes.AccreditedCourse | TrainingComponentTypes.Qualification | TrainingComponentTypes.TrainingPackage;
 Assert.IsTrue((tct & TrainingComponentTypes.AccreditedCourse) == TrainingComponentTypes.AccreditedCourse, "Expected AccreditedCourse as part the enum");

 Assert.IsFalse((tct & TrainingComponentTypes.SkillSet) == TrainingComponentTypes.SkillSet, "Found unexpected SkillSet as part the enum");

Наконец, при тестировании для всех, я пробовал обалогическое и побитовое сравнение, и оба возвращают одно и то же.Должен ли я использовать один над другим здесь?Например;

TrainingComponentTypes tct = TrainingComponentTypes.All;

Assert.IsTrue((tct & TrainingComponentTypes.All) == TrainingComponentTypes.All, "Expected All as part the enum");
Assert.IsTrue((tct) == TrainingComponentTypes.All, "Expected All as part the enum");
// The follow also pass the assertion for a value of All
Assert.IsTrue((tct & TrainingComponentTypes.Qualification) == TrainingComponentTypes.Qualification, "Expected Qualification as part the enum");
Assert.IsTrue((tct & TrainingComponentTypes.TrainingPackage) == TrainingComponentTypes.TrainingPackage, "Expected TrainingPackage as part the enum");

Итак, в заключение, я хотел бы знать следующее о побитовых перечислениях:

  1. Правильно ли мое понимание логического сравнения, учитывая мой пример выше?
  2. Является ли способ правильного выполнения побитового сравнения?
  3. Как правильно обрабатывать значение «Все» (побитовое или логическое).Я не уверен, получим ли мы когда-либо значение, в котором All было бы объединено с другими типами TrainingComponentTypes.Я не могу понять, почему мы это сделаем, но тогда, ты никогда не знаешь?
  4. Прав ли я, предполагая, что операторы switch в основном не должны использоваться для побитовых перечислений (учитывая, что ни один из них не является особым случаем и требует логического сравнения)?

СпасибоКрис

Ответы [ 6 ]

17 голосов
/ 24 мая 2011

Краткий ответ: Да:)

Longer:

1) Все операции выполняются над целочисленным значением переменной flags, так что вы можете думать о них с точки зрения этого.

2) Да.

3) Либо работает. Тем не менее, стоит отметить, что если кто-то вставит недопустимое значение в переменную, то версия == TrainingComponentTypes.All не будет выполнена. Например:

var badValue = (TrainingComponentTypes)128 | TrainingComponentTypes.All;
// now badValue != TrainingComponentTypes.All
// but (badValue & TrainingComponentTypes.All) == TrainingComponentTypes.All

Для этой части:

Я не уверен, что получим ли мы когда-либо значение, в котором All объединено с другими типами TrainingComponentTypes.

Я не уверен, что вы полностью понимаете, как работает enum под обложками.

The value of All is:
    127 = 1111111 (binary)

The other values are:
    AccreditedCourse       = 0000001
    Qualification          = 0000010
    Unit                   = 0000100
    SkillSet               = 0001000
    UnitContextualisation  = 0010000
    TrainingPackage        = 0100000
    AccreditedCourseModule = 1000000

Как видите, All - это просто побитовый | всех этих значений вместе. Вы не можете комбинировать любые другие типы TraningComponentTypes со всеми, потому что все уже включают их! Кроме того, если вы объединяете их все вместе с | самостоятельно, это точно так же, как и использование All напрямую (так что All - это просто удобство, когда вы определяете его внутри перечисления).

4) Вы могли бы использовать его для проверки None или All, но не для других значений.

Стоит отметить, что в Enum есть удобный метод, который будет выполнять эти проверки для вас: Enum.HasFlag .

8 голосов
/ 24 мая 2011

Правильно ли мое понимание логического сравнения, учитывая мой пример выше?

Да, логический в этом контексте означает операторы равенства и неравенства.

Является ли способ выполнения побитового сравнения правильным?

Да, но есть более простой способ: Enum.HasFlag. Например:

tct.HasFlag(TrainingComponentTypes.Qualification)

вместо:

(tct & TrainingComponentTypes.Qualification) == TrainingComponentTypes.Qualification

Как правильно обрабатывать значение «Все» (побитовое или логическое). Я не уверен, получим ли мы когда-либо значение, в котором All было бы объединено с другими типами TrainingComponentTypes. Я не понимаю, почему мы это сделали, но разве вы никогда не знаете?

Я думаю, что лучше определить All в самом enum как побитовое ИЛИ всех его частей. Но вы увидите, что люди делают это в обоих направлениях.

Прав ли я, предполагая, что операторы switch в основном не должны использоваться для побитовых перечислений (если ни один из них не является частным случаем и требует логического сравнения)?

Нет, совсем нет. Не стесняйтесь использовать их switch заявления. Значения case должны быть константами, но они могут быть выражениями и проверяться на равенство. Компилятор скажет вам, если вы делаете что-то глупое, например, попробуйте дважды использовать одно и то же значение case.

1 голос
/ 24 мая 2011
  1. Да.

  2. Да

  3. Можно использовать как логические, так и побитовые значения.Использование зависит от того, установлены ли все биты или только побитовое ИЛИ всех значений, которые вы определили.

  4. Да, но не из-за None.Коммутатор сравнивает одно значение, тогда как битовое поле может иметь несколько значений.

Как уже отмечали другие, Enum содержит HasFlag () .

0 голосов
/ 24 мая 2011

1 & 2 отлично смотрятся

3. Все, как вы это определили, не может быть объединено ни с чем без потери данных. Если «all» является реальным значением, которое вы ожидаете получить от сервера, вам, вероятно, следует изменить его на 128.

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

4. Можно использовать операторы Switch, но они не будут работать должным образом, если / когда у вас есть значения, имеющие более одного флага, если есть небольшие подмножества допустимых комбинаций флагов, они все равно могут быть полезны.

0 голосов
/ 24 мая 2011

"3. Как правильно обрабатывать значение" Все "(побитовое или логическое). Я не уверен, получим ли мы когда-либо значение, в котором значение All объединено с другими типами TrainingComponentTypes. Я не понимаю, почему мы бы, но "

Кажется, вы неправильно понимаете, как работают побитовые значения перечисления. «Все» всегда объединяется с другими значениями, фактически это комбинация всех значений. Глядя на двоичные значения для вашего перечисления:

Нет = 0,
AccreditedCourse = 1,
Квалификация = 10,
Единица = 100,
SkillSet = 1000,
UnitContextualisation = 10000,
TrainingPackage = 100000,
AccreditedCourseModule = 1000000,
Все = 1111111

это помогает вашему пониманию?

0 голосов
/ 24 мая 2011

1 и 2 - да, однако есть способ сделать его немного легче для чтения:

TrainingComponentTypes tct = TrainingComponentTypes.AccreditedCourse | TrainingComponentTypes.Qualification;
Assert.IsTrue(tct.HasFlag(TrainingComponentTypes.AccreditedCourse), "Expected AccreditedCourse as part the enum");

3 - я не уверен, нужно ли вообще значение AllЯ бы удалил его.

4 - Да, оператор switch обычно не имеет смысла для перечислений Flags.

...