C# Enum и Атрибут Flags

Возникают ситуации, когда переменная должна хранить несколько значений типа перечисления. Например, используемые области логирования: Warning + Info, или сочетания цветов: Red + Blue + Green.

Для хранения в переменной нескольких флагов, значениям енама присваиваются степени двойки:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

Значения 2, 4, 8 используются для операторов смещения, таких как побитовое И (AND), ИЛИ (OR) и исключающее ИЛИ (XOR).

Операции над переменной

Логическое ИЛИ (|) применяется для помещения нескольких значений флагов в одну переменную:

myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Логическое И (&) помогает при нахождении значения флага:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow has been set...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green has been set...
}

Начиная с .Net 4 можно использовать сокращенную версию, без явного указания &:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow has been set...
}

Операция XOR (’^’) исключает значения из переменной:

myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;
// Удаляем значение используя оператор смещения XOR.
myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Green;
Console.WriteLine("My colors are {0}", myProperties.AllowedColors);
// Output: My colors are Red, Blue

Атрибут Flags

Атрибут [Flags] необязательный и используется для красивого вывода при вызове .ToString():

enum Colors { Yellow = 1, Green = 2, Red = 4, Blue = 8 }
[Flags] enum ColorsFlags { Yellow = 1, Green = 2, Red = 4, Blue = 8 }
...
var str1 = (Colors.Yellow | Colors.Red).ToString(); // "5"
var str2 = (ColorsFlags.Yellow | ColorsFlags.Red).ToString(); // "Yellow, Red"

Так же, атрибут [Flags] не присваивает значениям степень двойки. Если не проставить вручную, то значения инициализируются как в обычном енаме.

Неправильное объявление:

[Flags]
public enum MyColors
{
    Yellow,
    Green,
    Red,
    Blue
}

Присвоенные значения: Yellow = 0, Green = 1, Red = 2, Blue = 3. Они не подходят для использования операций смещения.

Битовое представление

Описанное выше работает благодаря битовому представлению значений флагов при проставлении степени двойки:

Yellow: 00000001
Green:  00000010
Red:    00000100
Blue:   00001000

Значение переменной AllowedColors после присваивания Red, Green и Blue c использованием операции ИЛИ (|):

myProperties.AllowedColors: 00001110

Теперь, для проверки вхождения значения Green в переменную используем операцию смещения И (&):

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Это то же самое, что и MyColor.Green!

Полезные ссылки

Как тестировать абстрактные классы

Абстрактные классы сложно тестировать. Рассмотрим ситуации применения абстрактных классов и последующего рефакторинга для проведения юнит тестирования.

Выделить реальный интерфейс

Существует специализированный абстрактный класс, но все клиенты используют его конкретные реализации через единый публичный интерфейс:

Cитуация 1, до

Классы-потомки реализуют интерфейс, определенный абстрактными методами класса. Для повышения тестируемости этот интерфейс выделяется. Абстрактный класс превращается в конкретный и в его конструктор передается объект классов-потомков, реализующих выделенный интерфейс.

Применяется шаблон проектирования стратегия.

Cитуация 1, после

Бывший абстрактный класс теперь тестируется используя мок-объект нового интерфейса. Все просто.

Выделить хелпер

Абстрактный класс используется для исключения повторений в наследуемых классах. Классы-потомки используются напрямую.

Cитуация 2, до

Абстрактный класс работает как хелпер (helper).

Чтобы повысить тестируемость, не повторяющийся код переносится в существующие классы. Оставшиеся методы выделяются в хелпер и передаются через интерфейс в конструкторы конкретных классов.

Cитуация 2, после

Базовый класс удаляется. Этот способ снова приводит дизайн к конкретным классам, которые просто и легко тестировать.

Совет

Предпочитайте сложную сеть простых объектов над простой сетью сложных. Ключ к трестируемому коду — маленькие строительные блоки и слабая связанность.

Комбинация ситуаций

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

Когда в абстрактном классе часть методов виртуальные, а часть реализованные, еще возможно провести рефакторинг. Например, превратить классы наследники в стратегию. Но, такой случай — хороший индикатор, что ответственности требуют пересмотра.

Пишите в комментариях примеры когда сложно обойтись без абстрактного класса.