Перейти к содержимому
Теория · Перечисления, записи и паттерны · урок 1 из 8

Перечисления enum

Чему вы научитесь

Чему вы научитесь:

  • объявлять перечисление enum и выбирать одно значение из его списка;
  • описывать данные записью record и сравнивать записи по содержимому;
  • получать изменённую копию записи выражением with;
  • проверять значения паттернами в is и в выражении switch, помня, что ветви проверяются сверху вниз.
Подставка для штампов Small, Medium и Large: штамп Medium вынут, а на коробке рядом его отпечаток Medium

Представьте склад службы доставки. Размер коробки там не пишут от руки, а ставят штампом, и штампов на подставке ровно три: «маленькая», «средняя» и «большая». Своё слово штампом не поставишь, поэтому опечатки вроде «сердняя» на коробке не бывает.

Без штампов размер пришлось бы писать от руки, а в программе — хранить строкой или числом. Строка "Meduim" с опечаткой компилируется без единого предупреждения. Число 1 ничего не говорит читателю: это маленькая коробка или средняя?

Тип, у которого есть только заранее названные значения, называется перечислением. В C# его объявляют словом enum:

C#
Size box = Size.Medium;
Console.WriteLine(box); // Medium

if (box == Size.Medium)
{
    Console.WriteLine("medium box"); // medium box
}

enum Size { Small, Medium, Large }

Давайте разберём программу по строкам:

  1. enum Size { Small, Medium, Large } объявляет тип Size с тремя значениями. Как и класс, перечисление объявляют после инструкций верхнего уровня.
  2. Size box = Size.Medium; создаёт переменную типа Size. Значение пишут через точку: имя перечисления, затем имя значения.
  3. Console.WriteLine(box) печатает имя значения: Medium.
  4. box == Size.Medium сравнивает два значения перечисления и даёт true, поэтому выполняется ветка if.

Объявление перечисления

Разберём объявление enum Size { Small, Medium, Large } по частям:

  • enum — ключевое слово: после него идёт имя нового типа;
  • Size — имя типа, с большой буквы, как у класса;
  • Small, Medium, Large — члены перечисления, через запятую в фигурных скобках; их тоже называют с большой буквы.

Опечатку теперь поймает компилятор. Строка Size box = Size.Meduim; не скомпилируется: компилятор выдаст ошибку CS0117, потому что члена Meduim в типе Size нет.

Число за каждым именем

Внутри перечисление хранит целое число типа int. Члены нумеруются по порядку с нуля: Small — это 0, Medium — 1, Large — 2. Номер получают приведением (int), а обратно, от числа к члену, ведёт приведение к типу перечисления:

C#
Console.WriteLine((int)Size.Large);      // 2
Console.WriteLine((Size)1);              // Medium
Console.WriteLine((int)Status.Archived); // 6

enum Status { Draft = 1, Published = 5, Archived }

Номер можно задать и самому, как Draft = 1. Член без номера получает номер предыдущего члена плюс один, поэтому Archived — это 6.

На собеседовании любят спросить, что хранится внутри enum. Ответ: целое число, а имена нужны для чтения кода и для печати. Если не задать числу другой тип, это int. Значение по умолчанию у любого перечисления — 0. Если члена с номером 0 нет, как в Status, значение по умолчанию печатается цифрой 0.

Число без имени

Здесь аналогия со штампами перестаёт работать. Нельзя поставить штамп, которого нет на подставке. Переменная-перечисление так не защищена: приведением в неё можно положить любое число типа int, даже если у него нет имени.

C#
Size odd = (Size)7;
Console.WriteLine(odd); // 7

Console.WriteLine(odd switch
{
    Size.Small => "S",
    Size.Medium => "M",
    Size.Large => "L",
    _ => "unknown",
}); // unknown

Компилятор не проверяет, есть ли у числа имя: приведение явное, и за него отвечаете вы. Console.WriteLine печатает значение без имени цифрами, поэтому выводится 7.

Переменная-перечисление может хранить число без имени, поэтому выражению switch по перечислению нужна ветвь _, даже когда у каждого члена есть своя ветвь. Без неё компилятор выдаст предупреждение CS8524: число без имени, например (Size)3, не обработано.

Частая ошибка

Число положили в переменную-перечисление без приведения:

C#
Size box = 2; // ✗
Console.WriteLine(box);

enum Size { Small, Medium, Large }
TEXT
error CS0266: Cannot implicitly convert type 'int' to 'Size'. An explicit conversion exists (are you missing a cast?)

Программа не компилируется. Число не превращается в перечисление само, иначе случайная 2 тихо стала бы размером коробки. Нужно явное приведение (Size)2, а лучше — имя члена: Size.Large. Исключение одно — ноль: компилятор переводит литерал 0 или константу, равную нулю, в любое перечисление без приведения.

Главное

  • enum объявляет тип с фиксированным набором именованных значений: enum Size { Small, Medium, Large }.
  • За каждым членом стоит число типа int: по умолчанию 0, 1, 2 по порядку, и (int)Size.Large даёт 2.
  • Console.WriteLine печатает имя члена, а число без имени — цифрами: (Size)7 выводится как 7.
  • Число попадает в перечисление через явное приведение: Size box = 2; не компилируется.