You are on page 1of 17

ООП (Об'єктно-орієнтоване програмування) – парадигма програмування, в якій

основними концепціями є поняття об'єктів та класів.

Описом об'єкта є клас, а об'єкт є екземпляр цього класу. Можна ще провести таку
аналогію: у нас, у всіх, є деяке уявлення про людину, яка має ім'я, вік, якісь інші
характеристики. Тобто, певний шаблон - цей шаблон можна назвати класом. Конкретне
втілення цього шаблону може відрізнятися, наприклад, одні мають одне ім'я, інші - інше
ім'я. І реально існуюча людина (фактично екземпляр даного класу) представлятиме
об'єкт цього класу.

В принципі, раніше вже використовувалися класи. Наприклад, тип string, який є рядком,
фактично є класом. Або, наприклад, клас Console, у якого метод WriteLine() виводить
до консолі деяку інформацію. Тепер подивимося, як ми можемо визначати власні
класи.

Насправді клас представляє новий тип, який визначається користувачем. Клас


визначається за допомогою ключового слова сlass:

class назва_класу
{
// Вміст класу
}

Після слова class йде ім'я класу і далі у фігурних дужках йде власне вміст класу.
Наприклад, визначимо у файлі Program.cs клас Person, який представлятиме людину:

class Person
{}

Однак такий клас не є особливо показовим, тому додамо в нього деяку


функціональність.

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

Разом: клас – це конструкція мови, що складається із ключового слова class,


ідентифікатора (імені) та тіла. Клас може містити у своєму тілі: поля, методи,
властивості та події. Поля визначають стан, а методи поведінки майбутнього об'єкта.
Крім того, клас може визначати деяку поведінку або дії, що виконуються. Для
визначення поведінки у класі застосовуються методи.
Отже, додамо в клас Person поля та методи:

class Person
{
public string name = "Не визначено"; // ім'я
public int age; // вік
public void Print()
{
Console.WriteLine($"Ім'я: {name} Вік: {age}");
}
}

В даному випадку в класі Person визначено поле name, яке зберігає ім'я та поле age,
яке зберігає вік людини. На відміну від змінних, визначених методами, поля класу
можуть мати модифікатори, які вказуються перед полем. Так, в даному випадку, щоб усі
поля були доступні поза класом Person поля визначені модифікатором public.

Модифікатори доступу – private та public визначають видимість членів класу.


При визначенні полів ми можемо надати їм деякі значення, як у прикладі вище у разі
змінної name. Якщо поля класу не ініціалізовані, вони отримують значення за
замовчуванням. Для змінних числових типів число 0.
Також у класі Person визначено метод Print(). Методи класу мають доступ до його поля,
і в цьому випадку звертаємося до полів класу name та age для виведення їх значення
до консолі. І щоб цей метод був помітний поза класом, він також визначений з
модифікатором public.

Після визначення класу ми можемо створювати об'єкти. Для створення об'єкту


використовуються конструктори. По суті конструктори представляють спеціальні
методи, які називаються так само як і клас, які викликаються при створенні нового
об'єкта класу і виконують ініціалізацію об'єкта. Загальний синтаксис виклику
конструктора:

Створення екземпляра класу за сильним посиланням


назва_класу instance = new конструктор_класу(параметри_конструктора);

Створення екземпляра класу за слабким посиланням


new конструктор_класу(параметри_конструктора);

Спочатку йде оператор new, що виділяє пам'ять для об'єкта, а після нього йде виклик
конструктора.

Конструктор класу – спеціальний метод, що викликається під час побудови класу.


Якщо в класі не визначено жодного конструктора (як у випадку з нашим класом
Person), для цього класу автоматично створюється порожній конструктор за
замовчуванням, який не приймає жодних параметрів.

Тепер створимо об'єкт класу Person:

Person tom = new Person();

class Person
{
public string name = "Undefined";
public int age;

public void Print()


{
Console.WriteLine($"Name: {name} Age: {age}");
}
}

Для створення об'єкта Person використовується вираз new Person(). У результаті після
виконання цього висловлювання у пам'яті буде виділено ділянку, де зберігатимуться всі
дані об'єкта Person. А змінна Tom отримає посилання на створений об'єкт, і через цю
змінну ми можемо використовувати цей об'єкт і звертатися до його функціональності.
Для звернення до функціональності класу – полів, методів (а також інших елементів
класу) застосовується крапкова нотація – після об'єкта класу ставиться крапка, а потім
елемент класу:

об'єкт.поле_класу
об'єкт.метод_класу(параметри_метода)

Наприклад, звернемося до полів та методів об'єкта Person:

Person tom = new Person();

string personName = tom.name;


int personAge = tom.age;
Console.WriteLine($"Name: {personName} Age{personAge}");
tom.name = "Tom";
tom.age = 37;

tom.Print(); // Name: Tom Age: 37

class Person
{
public string name = "Undefined";
public int age;

public void Print()


{
Console.WriteLine($"Name: {name} Age: {age}");
}
}

Крім традиційних способів у мові C# передбачені особливі способи доступу, які


називають властивості. Вони забезпечують простий доступ до полів класів і структур.

Стандартний опис властивості має наступний синтаксис:


[модифікатори] тип_властивоості назва_властивості
{
get { дія, виконувані при отриманні значення властивості }
set { дії, що виконуються при встановленні значення властивості }
}

На початку визначення властивості можуть йти різні модифікатори, зокрема,


модифікатори доступу. Потім вказується тип властивості, після чого йде назва
властивості. Повне визначення властивості містить два блоки: get та set.
У блоці get виконуються дії отримання значення властивості. У цьому блоці за
допомогою оператора return повертаємо певне значення.
У блоці set встановлюється значення властивості. У цьому блоці за допомогою
параметра value ми можемо отримати значення, яке передано властивістю.
Блоки get і set ще називаються акссесорами чи методами доступу (до значення
властивості). Також їх називають - геттером і сеттером.
Тобто насправді властивість нічого не зберігає, воно виступає в ролі посередника між
зовнішнім кодом та змінною name.

Розглянемо приклад:

Person person = new Person();

person.Name = "Tom";

string personName = person.Name;


Console.WriteLine(personName); // Tom

class Person
{
private string name = "Undefined";

public string Name


{
get
{
return name;
}
set
{
name = value;
}
}
}

Тут у класі Person визначено приватне поле name, яке зберігає ім'я користувача, і є
загальнодоступна властивість Name. Хоча вони мають практично однакову назву за
винятком регістру, але це не більше, ніж стиль, назви у них можуть бути довільні і не
обов'язково повинні співпадати.
Через цю властивість ми можемо керувати доступом до змінної name. У властивості в
блоці get повертаємо значення поля:

get { return name; }

У блоці set встановлюємо значення змінної name. Параметр value представляє


значення, яке передається змінній name.

set { name = value; }

У програмі ми можемо звертатися до цієї властивості, як до звичайного поля. Якщо ми


йому присвоюємо якесь значення, то спрацьовує блок set, а значення, що передається,
передається в параметр value:

person.Name = "Tom";

Якщо ми отримуємо значення якості, то спрацьовує блок get, який насправді повертає
значення змінної name:

string personName = p.Name;

Можливо, може виникнути питання, навіщо потрібні властивості, якщо ми можемо у цій
ситуації обходитися звичайними полями класу? Але властивості дозволяють вкласти
додаткову логіку, яка може бути потрібна при встановленні або отриманні значення.
Наприклад, нам треба встановити перевірку за віком:

Person person = new Person();

Console.WriteLine(person.Age); // 1
person.Age = 37;
Console.WriteLine(person.Age); // 37
person.Age = -23;
Console.WriteLine(person.Age); // 37
class Person
{
int age = 1;
public int Age
{
set
{
if (value < 1 || value > 120)
Console.WriteLine("Вік має бути в діапазоні від 1 до 120");
else
age = value;
}
get { return age; }
}
}

У разі змінна age зберігає вік користувача. Безпосередньо ми не можемо звернутися до


цієї змінної - тільки через властивість Age. Причому в блоці set ми встановлюємо
значення якщо воно відповідає деякому розумному діапазону. Тому при передачі
властивості Age значення, яке не входить до цього діапазону, значення змінної не буде
змінюватися:

person.Age = -23;

Таким чином, властивість дозволяє опосередковувати та контролювати доступ до


даних об'єкта.
Блоки set і get не обов'язково одночасно мають бути присутніми у властивості. Якщо
властивість визначає лише блок get, то така властивість доступна лише для читання –
ми можемо отримати його значення, але не встановити.
І, навпаки, якщо властивість має тільки блок set, тоді ця властивість доступна тільки
для запису - можна встановити значення, але не можна отримати:

Person person = new Person();

Console.WriteLine(person.Name); // Tom

// person.Name = "Bob"; // !

person.Age = 37;
// Console.WriteLine(person.Age); // !

person.Print();

class Person
{
string name = "Tom";
int age = 1;

public int Age


{
set { age = value; }
}

public string Name


{
get { return name; }
}
public void Print()=> Console.WriteLine($"Name: {name} Age: {age}");
}

Тут властивість Name доступна лише читання, оскільки вона має лише блок get:

public string Name


{
get { return name; }
}

Ми можемо отримати його значення, але не можемо встановити:

Console.WriteLine(person.Name);
person.Name = "Bob"; // !

А властивість Age, навпаки, доступна тільки для запису, оскільки вона має лише блок
set:

public int Age


{
set { age = value; }
}

Можна встановити його значення, але не можна отримати:

person.Age = 37;
Console.WriteLine(person.Age); // !

Властивості обов'язково пов'язані з певною змінною. Вони можуть обчислюватися на


основі різних виразів

Person tom = new("Tom", "Smith");


Console.WriteLine(tom.Name); // Tom Smith
class Person
{
string firstName;
string lastName;
public string Name
{
get { return $"{firstName} {lastName}"; }
}
public Person(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
}

В даному випадку клас Person має властивість Name, яка доступна лише для читання і
яка повертає загальне значення на основі значень змінних firstName та lastName.

Ми можемо застосовувати модифікатори доступу не тільки до всієї властивості , але й


до окремих блоків get і set:

Person tom = new("Tom");

//tom.Name = "Bob"; // !
Console.WriteLine(tom.Name); // Tom
class Person
{
string name = "";
public string Name
{
get { return name; }

private set { name = value; }


}
public Person(string name) => Name = name;
}

Тепер закритий блок set ми зможемо використовувати тільки в даному класі – у його
методах, властивостях, конструкторі, але ніяк не в іншому класі:
При використанні модифікаторів у властивостях слід враховувати низку обмежень
● Модифікатор для блоку set або get можна встановити, якщо властивість має
обидва блоки (і set, і get)
● Тільки один блок set або get може мати модифікатор доступу, але не обидва
відразу
● Модифікатор доступу блоку set або get повинен бути більшим, ніж модифікатор
доступу властивості. Наприклад, якщо властивість має модифікатор public, то
блок set/get може мати тільки модифікатори protected internal, internal, protected,
private protected та private

Властивості керують доступом до полів класу. Однак якщо у нас з десяток і більше
полів, то визначати кожне поле і писати для нього однотипну властивість було б
стомлюючим. Тому в .NET було додано автоматичні властивості. Вони мають
скорочене оголошення:

class Person
{
public string Name { get; set; }
public int Age { get; set; }

public Person(string name, int age)


{
Name = name;
Age = age;
}
}

Насправді тут також створюються поля для властивостей, тільки їх створює не


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

Варто враховувати, що не можна створити автоматичну властивість тільки для запису,


як у випадку зі стандартними властивостями.
Автовластивості можна присвоїти значення за замовчуванням (ініціалізація
автовластивостей):

Person tom = new();

Console.WriteLine(tom.Name); // Tom
Console.WriteLine(tom.Age); // 37

class Person
{
public string Name { get; set; } = "Tom";
public int Age { get; set; } = 37;
}

І якщо ми не вкажемо для об'єкта Person значення властивостей Name та Age, то


діятимуть значення за замовчуванням.
Автовластивості також можуть мати модифікатори доступу:

class Person
{
public string Name { private set; get;}
public Person(string name) => Name = name;
}

Ми можемо прибрати блок set і зробити автовластивість доступною тільки для читання.
В цьому випадку для зберігання значення цієї властивості для нього неявно буде
створюватися поле з модифікатором readonly, тому слід враховувати, що подібні
get-властивості можна встановити або з конструктора класу, як у прикладі вище, або
при ініціалізації якості:

class Person
{
public string Name { get; } = "Tom";
public Person(string name) => Name = name;
}

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

Person tom = new Person();


tom.name = "Tom";
tom.age = 37;
tom.Print(); // Person: Tom - 37

Console.WriteLine(Person.type); // Person
// Person.type = "User"; // !

class Person
{
public const string type = "Person";
public string name = "Undefined";
public int age;
public void Print() => Console.WriteLine($"{type}: {name} - {age}");
}

Тут у класі Person визначено константу type, яка зберігає назву класу:

public const string type = "Person";

Назва класу залежить від об'єкта. Ми можемо створити багато об'єктів Person, але
назва класу від цього не повинна змінитись - вона відноситься до всіх об'єктів Person і
не повинна змінюватися. Тому назву типу можна зберегти як константи.
Варто зазначити, що константі відразу при її визначенні необхідно надати значення.
Подібно до звичайних полів ми можемо звертатися до константів класу всередині цього
класу. Наприклад, у методі Print значення константи виводиться на консоль.
Однак якщо ми хочемо звернутися до константи поза її класом, то для звернення
необхідно використовувати ім'я класу:

Console.WriteLine(Person.type); // Person

Раніше для створення об'єкта використовувався за замовчуванням конструктор. Проте


ми можемо визначити свої конструктори. Як правило, конструктор виконує ініціалізацію
об'єкта. Якщо у класі визначаються свої конструктори, він позбавляється конструктора
за умовчанням. Відповідно, якщо в класі є конструктор, і при цьому потрібно
створювати екземпляри класу з використанням конструктора за замовчуванням, то
конструктор за замовчуванням повинен бути визначений в тілі класу явно, інакше
виникне помилка на рівні компіляції.
Завдання конструктора за замовчуванням – ініціалізація полів за промовчанням.
Завдання користувача конструктора - ініціалізація полів зумовленими користувачем
значеннями.
На рівні коду конструктор представляє метод, який називається по імені класу, який
може мати параметри, але для нього не треба визначати тип, що повертається.
Наприклад, визначимо у класі Person найпростіший конструктор:

Person tom = new Person();

tom.Print();

class Person
{
public string name;
public int age;
public Person()
{
Console.WriteLine("Person");
name = "Tom";
age = 37;
}
public void Print()
{
Console.WriteLine($"Name: {name} Age: {age}");
}
}

Отже, тут визначено конструктор, який виводить до консолі деяке повідомлення та


ініціалізує поля класу.

public Person()
{
Console.WriteLine("Person");
name = "Tom";
age = 37;
}

Конструктори можуть мати модифікатори, які зазначаються перед ім'ям конструктора.


Так, в даному випадку, щоб конструктор був доступний поза класом Person, його
визначено з модифікатором public.
Визначивши конструктор, ми можемо викликати його для створення об'єкту Person:

Person tom = new Person();


В даному випадку вираз Person() якраз представляє виклик певного в класі
конструктора (це більше не автоматичний конструктор за замовчуванням, якого класі
тепер немає). Відповідно, при його виконанні до консолі буде виводитися рядок
"Person"
Подібним чином ми можемо визначати інші конструктори в класі. Наприклад, змінимо
клас Person наступним чином:

Person tom = new Person();


Person bob = new Person("Bob");
Person sam = new Person("Sam", 25);

tom.Print();
bob.Print();
sam.Print();

class Person
{
public string name;
public int age;
public Person() { name = "Неизвестно"; age = 18; } // 1
конструктор
public Person(string n) { name = n; age = 18; } // 2
конструктор
public Person(string n, int a) { name = n; age = a; } // 3
конструктор

public void Print()


{
Console.WriteLine($"Name: {name} Age: {age}");
}
}

Тепер у класі визначено три конструктори, кожен з яких набуває різної кількості
параметрів та встановлює значення полів класу. І ми можемо викликати один із цих
конструкторів для створення об'єкта класу.

Ключове слово this є посилання на поточний екземпляр/об'єкт класу. В яких ситуаціях


воно нам знадобиться?

Person sam = new("Sam", 25);


sam.Print();

class Person
{
public string name;
public int age;
public Person() { name = "Unk"; age = 18; }
public Person(string name) { this.name = name; age = 18; }
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public void Print() => Console.WriteLine($"Name: {name} Age: {age}");
}

У прикладі вище у другому та третьому конструкторі параметри називаються так само,


як і поля класу. І щоб розмежувати параметри та поля класу, до полів класу звернення
йде через ключове слово this. Так, у виразі

this.name = name;

Перша частина – this.name означає, що name – це поле поточного класу, а не назва


параметра name. Якби в нас параметри та поля називалися по-різному, то
використовувати слово це було б необов'язково. Також через ключове слово this можна
звертатися до будь-якого поля або методу.

У прикладі вище визначено три конструктори. Усі три конструктори виконують


однотипні дії – встановлюють значення полів name та age. Але цих дій, що
повторюються, могло бути більше. І ми можемо не дублювати функціональність
конструкторів, а просто звертатися з одного конструктора до іншого через ключове
слово this, передаючи потрібні значення для параметрів:

class Person
{
public string name;
public int age;
public Person() : this("Неизвестно")
{ }
public Person(string name) : this(name, 18)
{ }
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public void Print() => Console.WriteLine($"Name: {name} Age: {age}");
}

Отже, перший конструктор викликає другий, а другий конструктор викликає третій. За


кількістю та типом параметрів компілятор дізнається, який саме конструктор
викликається. Наприклад, у другому конструкторі:
public Person(string name) : this(name, 18)
{ }

йде звернення до третього конструктора, якому передаються два значення. Причому


на початку виконуватиметься саме третій конструктор, і лише потім код другого
конструктора.

Варто відзначити, що у прикладі вище практично всі конструктори не визначають


якихось інших дій, крім як передають третьому конструктору деякі значення. Тому в
реальності в даному випадку простіше залишити один конструктор, визначивши для
параметрів значення за замовчуванням:

Person tom = new();


Person bob = new("Bob");
Person sam = new("Sam", 25);

tom.Print();
bob.Print();
sam.Print();

class Person
{
public string name;
public int age;
public Person(string name = "Unk", int age = 18)
{
this.name = name;
this.age = age;
}
public void Print() => Console.WriteLine($"Name: {name} Age: {age}");
}

І якщо при виклику конструктора ми не передаємо значення якогось параметра, то


застосовується значення за замовчуванням.

Для ініціалізації об'єктів класів можна використовувати ініціалізатори. Ініціалізатори


представляють передачу у фігурних дужках значень доступним полям та властивостям
об'єкта:

Person tom = new Person { name = "Tom", age = 31 };


tom.Print(); // Name: Tom Age: 31

За допомогою ініціалізатора об'єктів можна надавати значення всім доступним полям


та властивостям об'єкта в момент створення. При використанні ініціалізації слід
враховувати наступні моменти:
● За допомогою ініціалізатора ми можемо встановити значення лише доступних із
позакласу полів та властивостей об'єкта. Наприклад, у прикладі вище поля
name і age мають модифікатор доступу public, тому вони доступні будь-якої
частини програми.
● Ініціалізатор виконується після конструктора, тому якщо і в конструкторі, і в
ініціалізаторі встановлюються значення тих самих полів і властивостей, то
значення, що встановлюються в конструкторі, замінюються значеннями з
ініціалізатора.

Ініціалізатори зручно застосовувати, коли поле або властивість класу представляє


інший клас:

Person tom = new Person{ name = "Tom", company = { title = "Microsoft"} };


tom.Print(); // Name: Tom Company: Microsoft

class Person
{
public string name;
public Company company;
public Person()
{
name = "Undefined";
company = new Company();
}
public void Print() => Console.WriteLine($"Name: {name} Company:
{company.title}");
}

class Company
{
public string title = "Unk";
}

Деконструктори (не плутати з деструкторами) дозволяють виконати декомпозицію


об'єкта на деякі частини.

Наприклад, нехай ми маємо наступний клас Person:

class Person
{
string name;
int age;
public Person(string name, int age)
{
this.name = name;
this.age = age;
}

public void Deconstruct(out string personName, out int personAge)


{
personName = name;
personAge = age;
}
}

В цьому випадку ми могли б виконати декомпозицію об'єкта Person так:

Person person = new Person("Tom", 33);

(string name, int age) = person;

Console.WriteLine(name); // Tom
Console.WriteLine(age); // 33

Значення змінним з деконструктора передаюсь за позицією. Тобто перше значення, що


повертається у вигляді параметра personName передається першою змінною - name,
друге значення - змінної age.
Насправді деконструктори це трохи більше, ніж синтаксичний цукор. Це все одно, що
якби ми написали:

Person person = new Person("Tom", 33);

string name; int age;


person.Deconstruct(out name, out age);

При отриманні значень декоструктора нам необхідно надати стільки змінних, скільки
деконструктор повертає значень. Проте, буває, що не всі ці значення потрібні. І замість
повертаються значень ми можемо використовувати прочерк _. Наприклад, нам треба
отримати лише вік користувача:

Person person = new Person("Tom", 33);

(_, int age) = person;

Console.WriteLine(age); // 33

Оскільки перше значення, що повертається - це ім'я користувача, яке не потрібно, в


цьому випадку замість змінної прочерк.
Завдання

Створити клас із ім'ям Address. У тілі класу потрібно створити поля: index, country, city,
street, house, apartment. Для кожного поля створити властивість з двома методами
доступу.

Створити екземпляр класу Address. У поля екземпляра записати інформацію про


поштову адресу. Виведіть на екран значення полів, що описують адресу.

Створити клас Converter. У тілі класу створити користувальницький конструктор, який


приймає три аргументи, і ініціалізує поля, що відповідають курсу 3-х основних валют, по
відношенню до гривні – public Converter (double usd, double eur, double rub). Написати
програму, яка виконуватиме конвертацію з гривні в одну із зазначених валют, також
програма повинна проводити конвертацію із зазначених валют у гривню.

Створити клас Employee. У тілі класу створити власний конструктор, який приймає два
рядкові аргументи, і ініціалізує поля, що відповідають прізвищу та імені співробітника.
Створити метод, що розраховує оклад співробітника (залежно від посади та стажу) та
податковий збір. Написати програму, яка виводить на екран інформацію про
співробітника (прізвище, ім'я, посада), оклад та податковий збір.

Створити клас User, що містить інформацію про користувача (логін, ім'я, прізвище, вік,
дату заповнення анкети). Поле “дата заповнення анкети” має бути проініціалізоване
лише один раз (при створенні екземпляра цього класу) без можливості його подальшої
зміни. Реалізуйте виведення на екран інформації про користувача.

You might also like