Тема 7. Перелічувані типи та структури

Автор

Юрій Клебан

Опубліковано

4/1/25

Keywords

enum, struct, типи значень, перелічуваний тип, структура, C#, value type, поле, метод, конструктор, immutable, mutable, Stack, Heap

Перелічувані типи (enum) та структури (struct) у C# є спеціальними типами значень, які використовуються для створення константних наборів значень і легковагових об’єктів відповідно. enum застосовується для визначення списків іменованих констант, що полегшує читабельність та використання коду. struct є схожим на клас, але зберігається у стеку та має меншу накладну витрату пам’яті, що робить його ефективним для невеликих об’єктів. Структури підтримують поля, методи, властивості та конструктори, але не можуть мати явно заданий конструктор без параметрів.


7.1. Перелічувані типи

7.1.1. Оголошення

Коли ви створюєте програму, часто буває зручно створити множину символьних імен для базових числових значень.

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

Лістинг 7.1. Визначення дня тижня за номером

int a = 4;
switch (a)
{
  case 1:
    Console.WriteLine("Понеділок");
    break;
  case 2:
    Console.WriteLine("Вівторок");
    break;
  case 3:
    Console.WriteLine("Середа");
    break;
  case 4:
    Console.WriteLine("Четвер");
    break;
  case 5:
    Console.WriteLine("Пятниця");
    break;
  case 6:
    Console.WriteLine("Субота");
    break;
  case 7:
    Console.WriteLine("Неділя");
    break;
} 

Проте подібний варіант заставляє програміста постійно пам’ятати, якій цифрі відповідає конкретний день тижня. Краще було б іменувати ці змінні.

У подібних ситуаціях використовуються перелічувані типи.

Перелічуваний тип (enum) - це визначений програмістом тип, який може приймати тільки обмежений набір значень.

За своїм внутрішнім представленням перелічуваний тип є цілим чимлом. Для перелічуваних типів затверджені наступні типи: byte, sbyte, short, ushort, int, uint, long або ulong.

Згалом перелічувані типи є одним із варіантів User Defined Type (UDT), тобто визначених користувачем типів. Такі типи у використанні не мають відмінностей від стандарних типів даних, як string, char, double тощо.

Загальний вигляд запису перелічуваного типу:

  модиф_доступу enum Назва : тип
  {
    Елемент1,
    Елемент2,
    ...,
    ЕлементN,
  } 

Модифікатори доступу можуть бути:

  • public – доступний для усіх.
  • private – доступний лише всередині поточного класу.

Розглянемо приклади опису перелічуваних типів:

Лістинг 7.2. Перелічуваний тип “Кольори”.

  public enum MyColors
  {
    Red,
    Yellow,
    Green
  }

Лістинг 7.3. Перелічуваний тип “Арифметичні операції”.

  public enum Operations
  {
    Plus,
    Minus,
    Divide,
    Multiply
  }

Для днів тижня перелічуваний тип матиме вигляд:

Лістинг 7.4. Перелічуваний тип “Дні тижня”.

  enum Days
  {
    Mon,  //0
    Tue,  //1
    Wed,  //2
    Thu,  //3
    Fri,  //4
    Sat,  //5
    Sun   //6
  } 

7.1.2. Заміна значень

Перелічувальний тип Days визначає чотири іменовані константи, відповідні деяким числовим значенням. У С# перший елемент за замовчуванням має нульовий індекс (\(0\)) і далі по наростаючій (\(n + 1\)).

Ви можете змінити цю поведінку, як вам потрібно:

Лістинг 7.5. Перелічуваний тип “Дні тижня” зі зміною цілочисельних значень.

  enum Days
  {
    Mon = 10,
    Tue,  //11
    Wed,  //12
    Thu,  //13
    Fri,  //14
    Sat,  //15
    Sun   //16
  }

Номери елементів перелічувального типу не обов’язково мають бути послідовними. Якщо (з будь-якої причини) є сенс визначити Days таким чином, компілятор заперечувати не буде:

Лістинг 7.6. Перелічуваний тип “Дні тижня” зі зміною цілочисельних значень.

  enum Days
  {
    Mon = 10,
    Tue,    //11
    Wed = 75,
    Thu = 100,
    Fri,    //101
    Sat,    //102
    Sun     //103
  }

Для зберігання кожного елементу перелічувального типу за замовчуванням використовується клас System.Int32. Ви також можете змінити цю поведінку. Наприклад, якщо ви хочете, аби внутрішні елементи Days зберігалися як значення типу byte, а не як int, можна написати наступне:

Лістинг 7.7. Перелічуваний тип “Дні тижня” унаслідуваний від byte.

  enum Days : byte
  {
    Mon = 10,
    ...,
    Sat,
    Sun
  } 

Розглянемо приклад задачі про визначення днів тижня уже з використанням типу Days:

Лістинг 7.8. Приклад розв’язання задачі з використанням типу Days.

  enum Days
  {
     Mon = 1, 
    Tue,    //2
    Wed,    //3
    Thu,    //4
    Fri,    //5
    Sat,    //6
    Sun     //7
  }

  static void Main(string[] args)
  {
    Days day = Days.Thu;
    switch (day)
    {
      case Days.Mon:
        Console.WriteLine("Понеділок");
        break;
      case Days.Tue:
        Console.WriteLine("Вівторок");
        break;
      case Days.Wed:
        Console.WriteLine("Середа");
        break;
      case Days.Thu:
        Console.WriteLine("Четвер");
        break;
      case Days.Fri:
        Console.WriteLine("Пятниця");
        break;
      case Days.Sat:
        Console.WriteLine("Субота");
        break;
      case Days.Sun:
        Console.WriteLine("Неділя");
        break;
    }
  } 

Результат роботи програми:

Четвер

Даний приклад є лише демонстраційним і створювати такий перелічуваний тип немає жодної потреби, адже існує уже вбудований аналогічний тип DayOfWeek.

Перепишіть попереденю програму із використанням стандартного перелічуваного типу даних DayOfWeek.


7.1.3. Перетворення

Перелічувані типи можуть бути успішно конвертовані у числа, а також зчитані з тектових та числових значень.

Якщо для enum-у оголошеному у лістингу 7.8 використати наступний код:

Лістинг 7.9. Перетворення типу даних Days до int.

  Days day = Days.Tue;
  Console.WriteLine(day);
  Console.WriteLine((int)day);

Результат роботи програми матиме вигляд:

  Tue
  2

Можливий також і зворотній варіант:

Лістинг 7.10. Перетворення типу даних int до Days.

  Days day = (Days)5;
  Console.WriteLine(day);

Результат роботи програми матиме вигляд:

  Fri

Також для роботи з перелічуваними типами варто скоритатися класом Enum, який має ряд корисних методів та властивостей. Коротко розглянемо один із них, а саме Enum.Parse<>(). Цей метод дозволяє введений (у вигляді рядка) текст перетворити в об’єкт перелічуваного типу.

Лістинг 7.11. Перетворення типу даних string до Days за допомогою Enu.Parse<>().

  Console.OutputEncoding = Encoding.Unicode;
  Console.Write("Введіть значення enum:\t");
  Days day = Enum.Parse<Days>(Console.ReadLine());
  Console.WriteLine("Enum:\t\t" + day);
  Console.WriteLine("Integer:\t" + (int)day);

Результат роботи програми матиме вигляд:

  Введіть значення enum:  Sun //вводиться користувачем
  Enum:           Sun
  Integer:        7
Warning

Детальніше про клас Enum можна ознайомитися у документації на сайті компанії Microsoft.


7.2. Робота зі структурами

7.2.1. Оголошення

У C# існує багато різних типів даних для представлення інформації. Проте всі об’єкти реального світу описати розробникам усе-таки не вдалося :smile:. Наприклад, якщо потрібно описати поняття студент, квартира, веб-сайт, комп’ютер та ін. є потреба створювати користувацькі типи даних.

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

Структури в C# практично нічим не відрізняються від структур на будь-яких інших мовах. Відмінності спостерігаються лише на більш низькому рівні. В основному це стосується того, що для структур в C# не існує базового класу. Але в той же час структури є похідними від типу ValueType.

Дуже узагальнивши поняття структури можна визначити його, як згруповану сукупність ознак(даних) певного об’єкта, методів управління цими ознаками та доступу до них. Структури визначаються за допомогою ключового слова struct, наприклад:

  модиф_доступу struct Назва
  {
    // Поля, властивості, методи...
  } 

Структури використовують велику частину того ж синтаксису, що і класи, проте вони більш обмежені в порівнянні з ними:

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

  • Структура може не оголошувати використовуваний за замовчуванням конструктор (конструктор без параметрів) або деструктор.

  • Структури копіюються при присвоєнні. При присвоєнні структури у нову змінну виконується копіювання всіх даних, а будь-яка зміна нової копії не впливає на дані у вихідній копії.

  • Структури є значимими типами, а класи - посилальними типами.

  • Структури можуть бути створені без використання оператора new.

  • Структури можуть оголошувати конструктори, що мають параметри.

  • Структура не може наслідуватися від іншої структури або класу і не може бути основою для інших класів. Всі структури наслідуються безпосередньо від System.ValueType, який наслідується від System.Object.

  • Структури можуть реалізовувати інтерфейси.

  • Структура може використовуватися як тип, що допускає значення NULL, і їй можна призначити значення NULL.

Структура - це набір залежних один від одного змінних. Залежність тут виключно логічна і визначається умовами задачі. Аби стало зрозуміло, розглянемо простий приклад.

7.2.2. Поля

Припустимо, ми пишемо програму, що друкує довідки для студентів. Всі довідки мають один і той же вигляд і текст, окрім наступних полів: імя, прізвище, курс, факультет, дата народження. Це залежні дані і їх можна представити у вигляді структури, наприклад так:

Лістинг 7.12. Опис структури Student.

  struct Student
  {
    public string _firstName;
    public string _lastName; 
    public DateTime _dateOfBirth;
    public string _faculty;
    public int _course;
  }

Усі елементи у представленій вище структурі є полями. Модифікатор доступу public до полів означає, що доступ до цього поля є повним у всіх блоках програми. На рис. 7.1. зображено різницю, коли поле оголошено private, і коли public.

Рис. 7.1. Відмінність у доступі до поля залежно від модифікатора public/private

Наша структура називається Student і має 5 полів. Після того, як структура оголошена, ми можемо з нею працювати.

Розглянемо приклад: потрібно вивести довідку з інформацією про студента.

Лістинг 7.13. Приклад роботи зі структурою Student.

  struct Student
  {
      public string firstName;
      public string lastName;
      public DateTime dateOfBirth;
      public string faculty;
      public int course;
  }
  
  static void Main(string[] args)
  {
      Console.OutputEncoding = Encoding.Unicode;
      Student student = new Student();
      student.firstName = "Дмитро";
      student.lastName = "Попов";
      student.faculty = "гуманітарний";
      student.course = 3;
      student.dateOfBirth = DateTime.Parse("02/05/1990");
  
      Console.WriteLine("\t\tДОВIДКА");
      Console.WriteLine(" пiдтвреджує, що");
      Console.WriteLine(" {0} {1} дiйсно навчається на {2}-му курсі.",
          student.firstName, student.lastName, student.course);
      Console.WriteLine(" Дата народження: " + student.dateOfBirth.ToShortDateString());
      Console.WriteLine(" Факультет: " + student.faculty);
  }

Результат роботи програми матиме вигляд:

                ДОВIДКА
 пiдтвреджує, що
 Дмитро Попов дiйсно навчається на 3-му курсі.
 Дата народження: 2/5/1990
 Факультет: гуманітарний

7.2.4. Методи

Структури підримують також і методи. Виведення інформації про студента можна винести у метод структури і викликати у програмі. Для цього створимо метод Print. Також напишемо метод, який буде повертати повне ім’я студента, наприклад, Попов Дмитро.

Лістинг 7.14. Приклад роботи зі структурою Student. Додавання методів.

  struct Student
  {
      public string firstName;
      public string lastName;
      public DateTime dateOfBirth;
      public string faculty;
      public int course;
  
      public void Print()
      {
          Console.WriteLine("\t\tДОВIДКА");
          Console.WriteLine(" пiдтвреджує, що");
          Console.WriteLine(" {0} {1} дiйсно навчається на {2}-му курсі.",
              firstName, lastName, course);
          Console.WriteLine(" Дата народження: " + dateOfBirth.ToShortDateString());
          Console.WriteLine(" Факультет: " + faculty);
      }
  
      public string GetFullName()
      {
          return firstName + " " + lastName;
      }
  }
  
  static void Main(string[] args)
  {
      Console.OutputEncoding = Encoding.Unicode;
      Student student = new Student();
      student.firstName = "Дмитро";
      student.lastName = "Попов";
      student.faculty = "гуманітарний";
      student.course = 3;
      student.dateOfBirth = DateTime.Parse("02/05/1990");
  
      student.Print();
      Console.WriteLine("\n " + student.GetFullName() + "\n");
  }

Результат роботи програми матиме вигляд:

                ДОВIДКА
 пiдтвреджує, що
 Дмитро Попов дiйсно навчається на 3-му курсі.
 Дата народження: 2/5/1990
 Факультет: гуманітарний

 Дмитро Попов

7.2.5. Конструктори

Структури підтримують також і конструктори.

У об’єктно-орієнтованому програмуванні конструктор класу (від англ. constructor, інколи скорочують ctor) - спеціальний блок інструкцій, що викликається при створенні об’єкту з використанням ключового слова new.

Конструктор схожий з методом, але відрізняється від методу тим, що не має явно оголошеного типу повертаємого значення, не наслідується. Конструктори виділяються наявністю однакового імені з ім’ям класу, в якому оголошуються. Конструкторів може бути одразу кілька.

Лістинг 7.15. Приклад роботи зі структурою Student. Додавання конструктора.

  struct Student
  {
      // Поля
      public string firstName;
      public string lastName;
      public DateTime dateOfBirth;
      public string faculty;
      public int course;
  
      // Конструктор
      public Student(string _firstName, string _lastName, DateTime _dateOfBirth, string _faculty, int _course)
      {
          this.firstName = _firstName;
          this.lastName = _lastName;
          dateOfBirth = _dateOfBirth;
          faculty = _faculty;
          course = _course;
      }
  
      // Методи
      public void Print()
      {
          Console.WriteLine("\t\tДОВIДКА");
          Console.WriteLine(" пiдтвреджує, що");
          Console.WriteLine(" {0} {1} дiйсно навчається на {2}-му курсі.",
              firstName, lastName, course);
          Console.WriteLine(" Дата народження: " + dateOfBirth.ToShortDateString());
          Console.WriteLine(" Факультет: " + faculty);
      }
  
      public string GetFullName()
      {
          return firstName + " " + lastName;
      }
  }
  
  static void Main(string[] args)
  {
      Console.OutputEncoding = Encoding.Unicode;
      Student student = new Student("Дмитро", "Попов", DateTime.Parse("02/05/1990"), "гуманітарний", 3);
      student.Print();
  }

Результат виконання аналогічних до результату з лістингу 7.13.


7.2.6. Масив об’єктів

Якщо порівняти приклад та 7.13, то коду приблизно однаково. Але при зростанні кількості обєктів типу Студент буде видно суттєву оптимізацію. Напишемо програму, яка виводить на екран довідки 3-х студентів.

Лістинг 7.16. Приклад роботи зі структурою Student.

  static void Main(string[] args)
  {
    Console.OutputEncoding = Encoding.Unicode;
     
    Student[] group = new Student[3];
    group[0] = new Student("Дмитро", "Попов", DateTime.Parse("02/05/1990"), "економічний", 3);
    group[1] = new Student("Іван", "Петров", DateTime.Parse("02/05/1990"), "економічний", 3);
    group[2] = new Student("Олена", "Чуприна", DateTime.Parse("02/05/1990"), "економічний", 3);
  
    foreach (Student student in group)
      student.Print();
  }

Результат роботи програми матиме вигляд:

                ДОВIДКА
 пiдтвреджує, що
 Дмитро Попов дiйсно навчається на 3-му курсі.
 Дата народження: 2/5/1990
 Факультет: економічний
                ДОВIДКА
 пiдтвреджує, що
 Іван Петров дiйсно навчається на 3-му курсі.
 Дата народження: 2/5/1990
 Факультет: економічний
                ДОВIДКА
 пiдтвреджує, що
 Олена Чуприна дiйсно навчається на 3-му курсі.
 Дата народження: 2/5/1990
 Факультет: економічний

Детальніше про роботу з обєктами буде розглянуто в Темі 8. ООП.


Задачі

Задача 7.1

Написати програму, що дозволяє маніпулювати даними про товари у магазині. Товар описати як окрему структуру (struct) з полями:

  • Назва (Title)
  • Категорія (Category)
  • Ціна (Price)
  • Кількість (Quantity)

Категорії товарів визначені як перелічуваний тип (enum) і мають наперед визначені значення, наприклад:

  • Промислові товари (Industrial products)
  • Побутова хімія (Household chemicals)
  • Продукти харчування (Food)

Реалізувати функції:

  • Вивести список товарів
  • Додавати товар
  • Редагувати товар
  • Видалити товар
  • Вийти з програми

Взаємодію з користувачем реалізувати через меню.


Задача 7.2

Описати структуру Книга (Book) з даними:

  • Назва
  • Автор
  • Рік видання
  • Місце видання

Реалізувати метод для виведення інформації про книгу, наприклад Print(), який приймає 1 параметр showFullInfo типу bool і в залежності від його значення виводить інформацію про книгу у форматі:

  • якщо showFullInfo == true, то виводиться стрічка з інформацією про усі поля книги;
  • якщо showFullInfo == false, то виводиться стрічка з інформацією тільки про поля Назва та Автор.

Автора реалізувати як окрему структуру з полями:

  • Імя
  • Прізвище
  • Дата народження

Створити масив з 5-ти книг та вивети його на консоль методом Print() з різними параметрами.

Задача 7.3

На основі структур, створених у завданні 7.2написати програму для маніпулюванням інформації про книги у каталозі.


Задача 7.4

Написати програму для маніпулювання даними про товари у магазині. Кожен товар повинен обов’язково описуватися полями:

  • Артикул (ідентифікатор товару)
  • Назва
  • Бренд
  • Ціна
  • Залишок на складі (кількість)

Бренди товарів реаліузвати як перелічуваний тип.

Реалізувати меню:

  • Список товарів
  • Продати товар
  • Вивести товари по бренду
  • Додати товар у каталог
  • Вийти

Контрольні запитання

Перелічувані типи (enum)

  1. Що таке enum у C#?
  2. Який базовий тип даних використовується для значень enum за замовчуванням?
  3. Як явно змінити базовий тип для enum?
  4. Чи можна привласнити конкретні значення елементам enum? Наведіть приклад.
  5. Як отримати числове значення елемента enum?
  6. Як перетворити рядок у відповідний елемент enum?
  7. Чи можна використовувати enum у виразах switch?
  8. Як отримати всі можливі значення enum програмно?
  9. Чи можна використовувати enum у якості параметра методу? Наведіть приклад.
  10. Яка різниця між enum і const у C#?

Структури (struct)

  1. Що таке struct у C#?
  2. Чим структура відрізняється від класу?
  3. Як створити власну структуру у C#? Наведіть приклад.
  4. Де зберігаються об’єкти структур і класів у пам’яті (Stack/Heap)?
  5. Чи можуть структури містити методи? Наведіть приклад.
  6. Чи можна створити конструктор без параметрів у структурі? Чому?
  7. Чи можна використовувати struct як параметр методу? Що відбувається при передачі?
  8. Які модифікатори доступу можна використовувати для полів у структурі?
  9. Чи можна створити структуру без жодного поля?
  10. Чи можуть структури реалізовувати інтерфейси?

Робота зі структурами та enum

  1. Як створити масив enum і перебрати його елементи?
  2. Чи можна успадковувати struct від іншого struct? Чому?
  3. Як передати struct за посиланням?
  4. Що відбудеться, якщо змінити значення поля структури у методі?
  5. Чому структури підходять для невеликих об’єктів, а класи – для складніших?
  6. Чи можна оголосити enum або struct всередині іншого struct?
  7. Які обмеження має структура порівняно з класом?
  8. Чи можуть структури містити статичні поля або методи?
  9. Чому struct не може мати деструктор?
  10. Коли краще використовувати struct, а коли class?

Список використаних джерел

  1. Albahari, J., & Albahari, B. (2012). C# 5.0 in a Nutshell, Fifth Edition. 1005 Gravenstein Highway North, Sebastopol, USA: O’Reilly Media, Inc.
  2. Cardelli, L., & Wegner, P. (December 1985 p.). On Understanding Types, Data Abstraction, and Polymorphism. ACM Computing 1. Surveys, 17(4), сс. 149-154. Отримано з http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf
  3. Kort, W. d. (2013). Exam Ref 70-483: Programming in C#. Sebastopol, California 95472: O’Reilly Media, Inc.
  4. MSDN. (без дати). Отримано з Microsoft Developer Network: http://msdn.microsoft.com/
  5. Биллиг, А. (2005). Основы программирования на C#. Москва, Россия: НОУ ИНТУИТ.
  6. Нейгел, К. И. (2011). С# 4.0 и платформа .NET 4 для профессионалов. Москва, Россия: ООО “И.Д. Вильямс”.
  7. Павловская, Т. (2009). С#. Программирование на языке высокого уровня. Учебник для вузов. СПб, Россия: Питер.

Warning

Дорогі друзі, якщо Ви помітили, що для написання матеріалів використані джерела, які я не вказав - прошу надіслати мені інформацію на пошту. Дякую.