Коли та навіщо використовувати вкладені класи?
Використовуючи об'єктно-орієнтоване програмування, ми маємо можливість створити клас усередині класу (вкладений клас), але я ніколи не створював вкладений клас за 4 роки свого досвіду програмування.
Навіщо потрібні вкладені класи?
Я знаю, що клас може бути помічений як закритий, якщо він вкладений, і що ми можемо отримати доступ до всіх закритих членів цього класу з класу, що містить. Ми могли б просто помістити змінні як приватні в клас, що сам містить.
То навіщо створювати вкладений клас?
У яких сценаріях мають використовуватися вкладені класи чи більш потужні з погляду використання проти іншими методами?
У вас є кілька добрих відповідей, а іноді у мене просто є робочий клас або стійка, які мені потрібні лише всередині класу.
Відповіді:
Головною особливістю вкладених класів і те, що можуть отримати доступом до закритим членам зовнішнього класу, маючи у своїй всю міць самого класу. Також вони можуть бути приватними, що дозволяє за деяких обставин виконувати досить потужну інкапсуляцію:
Тут ми повністю блокуємо установник до фабрики, оскільки клас є приватним, жоден споживач не може зменшити його та отримати доступ до установника, і ми можемо повністю контролювати те, що дозволено.
public
interface
IFoo
int
Fooget;>
>
public
class
Factory
private
class
MyFoo
:
IFoo
public
int
Fooget;set;>
>
public
IFoo
CreateFoo(int
value)
=>
new
MyFooFoo
=
value>;
>
Крім цього, це корисно для реалізації сторонніх інтерфейсів у контрольованому середовищі, де ми досі можемо отримати доступ до закритих членів.
Якби ми, наприклад, надали екземпляр деякого інтерфейсу іншому об'єкту, але ми не хочемо, щоб наш основний клас реалізував його, ми могли б дозволити внутрішньому класу реалізувати його.
public
class
Outer
private
int
_example;
private
class
Inner
:
ISomeInterface
Outer
_outer;
public
Inner(Outer
outer)_outer
=
outer;>
public
int
DoStuff()
=>
_outer._example;
>
public
void
DoStuff()_someDependency.DoBar(new
Inner(this));
>
>
У більшості випадків я очікую, що делегати будуть чистішим способом робити те, що ви показуєте у другому прикладі
@ BenAaronson, як би ви реалізували випадковий інтерфейс з використанням делегатів?@EsbenSkovPedersen Ну, для вашого прикладу, замість того, щоб передати екземпляр Outer , ви повинні передати Func
@BenAaronson у цьому надзвичайно простому випадку ви маєте рацію, але для складніших прикладів занадто багато делегатів стає незграбним.
@EsbenSkovPedersen: Ваш приклад має деякі переваги, але IMO слід використовувати тільки в тих випадках, коли створення Inner не вкладено та internal не працює (тобто коли ви не маєте справу з різними збірками). Хіт читання від класів вкладеності робить його менш сприятливим, ніж використання internal (де це можливо).
Як правило, вкладений клас N створюється всередині класу C щоразу, коли C потрібно використовувати щось внутрішньо, що ніколи не повинно (безпосередньо) використовуватися поза C, і з будь-якої причини, що щось має бути об'єктом нового типу, а не будь-яким існуючим типом.
Я вважаю, що найчастіше це відбувається при реалізації методу, який повертає об'єкт, який реалізує деякий інтерфейс, і ми хочемо приховати конкретний тип цього об'єкта, тому що він більше ніде не буде корисним.
Реалізація IEnumerable є гарним прикладом цього:
class
BlobOfBusinessData:
IEnumerableBusinessDatum>
public
IEnumeratorBusinessDatum>
GetEnumerator()
return
new
BusinessDatumEnumerator(. );
>
class
BusinessDatumEnumerator:
IEnumeratorBusinessDatum>
.
>
>
У когось за межами просто немає причин BlobOfBusinessData знати або піклуватися про конкретний тип BusinessDatumEnumerator, тому ми могли б залишити його всередині BlobOfBusinessData .
Це не призначалося для того, щоб бути прикладом «найкращої практики» для IEnumerable правильної реалізації просто для того, щоб донести ідею до кінця, тому я пропустив такі речі, як явний IEnumerable.GetEnumerator() метод.
Інший приклад, який я використовую з новими програмістами, - це клас Node в LinkedList . Будь-хто, хто використовує LinkedList , не дбає про те Node , як він реалізований, якщо він може отримати доступ до вмісту. Єдина сутність, яка піклується взагалі - це LinkedList сам клас.
То навіщо створювати вкладений клас?
Я можу згадати кілька важливих причин:
1. Включити інкапсуляцію
Багаторазово вкладені класи є деталями реалізації класу. Користувачі основного класу не повинні піклуватися про їхнє існування. Ви повинні мати можливість змінювати їх на свій розсуд, не вимагаючи від користувачів основного класу змінювати свій код.
2. Уникайте забруднення імені
Не слід додавати типи, змінні, функції тощо. Д. В області, якщо вони не підходять для цієї області. Це трохи відрізняється від інкапсуляції. Може бути корисно розкрити інтерфейс вкладеного типу, але належне місце для вкладеного типу, як і раніше, залишається основним класом. У землі C++ типи ітераторів є одним із таких прикладів. Я не маю достатньо досвіду в C #, щоб дати вам конкретні приклади.
Давайте створимо спрощений приклад, щоб проілюструвати, чому переміщення вкладеного класу на ту саму область, як і основний клас, є забрудненням імені.Допустимо, ви реалізуєте клас пов'язаного списку. Зазвичай ви б використали
publid
class
LinkedList
class
Node
.
>
// Use Node до здійснити LinkedList class.
>
Якщо ви вирішите перейти Node на ту ж область LinkedList, що і у вас буде
public
class
LinkedListNode
>
public
class
LinkedList
// Use LinkedListNode to implement the class
>
LinkedListNode навряд чи буде корисним без LinkedList самого класу. Навіть якщо LinkedList надані деякі функції, які повертають LinkedListNode об'єкт, який LinkedList може використовувати користувач, він все одно стає LinkedListNode корисним тільки при використанні LinkedList. З цієї причини, перетворення класу «вузол» на клас рівноправного вузла LinkedList забруднює область дії.
- Я використовую публічні вкладені класи для пов'язаних допоміжних класів.
public
class
MyRecord
// stuff
public
class
Comparer
:
IComparerMyRecord>
>
public
class
EqualsComparer
:
IEqualsComparerMyRecord>
>
>
MyRecord[]
array;
Arrays.sort(array,
new
MyRecord.Comparer());
// Class that may or may not be mutable.
public
class
MyRecord
protected
string
name;
public
virtual
String
Name
get
=>
name;
set
=>
throw
new
InvalidOperation();
>
public
Mutable
public
override
String
get
=>
name;
set
=>
name
=
value;
>
>
>
MyRecord
mutableRecord
=
new
MyRecord.Mutable();
Той, хто дзвонить може вибрати, яка версія підходить для якої проблеми. Іноді клас не може бути повністю створений за один прохід і вимагає версії, що змінюється. Це завжди правильно при обробці циклічних даних. Змінювані файли можуть бути перетворені на тільки для читання пізніше.
-
Я використовую їх для внутрішніх записів
public
class
MyClass
ListLine>
lines
=
new
ListLine>();
public
void
AddUser(
string
name,
string
address
)
=>
lines.Add(new
Line
Name
=
name,
Address
=
address
>);
class
Line
string
Name;
string
Address;
>
>
Вкладений клас можна використовувати щоразу, коли ви хочете створити більше одного екземпляра класу або коли ви хочете зробити цей тип доступнішим.
Вкладений клас збільшує інкапсуляцію, а також призведе до більш зручного для читання і підтримуваного коду.
Загальні відомості про класи
Тип, визначений як class посилальний тип. Під час виконання при оголошенні змінної типу посилання змінна містить значення null , поки явно не створіть екземпляр класу за допомогою new оператора або призначте його об'єкт сумісного типу, створеного в іншому місці, як показано в наступному прикладі:
//Declaring an object of type MyClass. MyClass mc = New MyClass(); //Визначення іншого об'єкта в той самий тип, що визначає його значення з першого об'єкта. MyClass mc2 = mc;
При створенні об'єкта виділяється достатній обсяг пам'яті для цього об'єкта в купі, і змінна зберігає тільки посилання на розташування даного об'єкта. Пам'ять, що використовується об'єктом, звільняється функцією автоматичного керування пам'яттю середовища CLR, що називається збиранням сміття. Для отримання додаткових відомостей про складання сміття див. у розділі Автоматичне керування пам'яттю та складання сміття.
Оголошення класів
Класи оголошуються за допомогою ключового class слова, за яким слідує унікальний ідентифікатор, як показано в наступному прикладі:
//[access modifier] - [class] - [identifier] Public class Customer < // Fields, properties, methods and events go here. >
Додатковий модифікатор доступу передує ключовому слову class. Стандартний доступ для class типу internal . Оскільки public використовується в цьому випадку, будь-який користувач може створювати екземпляри цього класу. За ім'ям класу слідує ключове слово class . Ім'я класу має бути припустимим ім'ям ідентифікатора C#.Частина визначення, що залишилася, - це тіло класу, в якому задаються дані і поведінка. членами класу.
Створення об'єктів
Хоча іноді вони використовуються як взаємозамінні, клас і об'єкт відрізняються.
Об'єкти можна створити за допомогою нового ключового слова, за яким слідує ім'я класу, як показано нижче.
Customer object1 = новий Customer();
При створенні екземпляра класу посилання на об'єкт передається програмісту У попередньому прикладі object1 є посилання на об'єкт, який засновано на Customer . :
Ми не рекомендуємо створювати посилання на об'єкти, які не посилаються на об'єкт, оскільки спроба доступу до об'єкта через це посилання завершується збоєм під час виконання Посилання може посилатися на об'єкт, створивши новий об'єкт або призначивши його існуючому об'єкту, наприклад:
Customer object3 = new Customer(); Customer object4 = object3;
У цьому коді створюються два посилання на об'єкт, які вказують на той самий об'єкт. Таким чином, будь-які зміни об'єкта, виконані за допомогою object3, відображаються при подальшому використанні object4 . .
Конструктори та ініціалізація
У попередніх розділах представлений синтаксис для оголошення типу класу та створення екземпляра цього типу.
- Прийміть значення за промовчанням
- Ініціалізатори полів
- Параметри конструктора
- Ініціалізатори об'єктів
Кожен тип .NET має значення за промовчанням. Як правило, це значення дорівнює 0 для типів чисел і null для всіх типів посилань. Ви можете покладатися на це значення за умовчанням, якщо воно є розумним у додатку.
Якщо значення за промовчанням для .NET не є правильним, можна встановити початкове значення за допомогою ініціалізатора полів:
public class Container < // Initialize capacity field to default value of 10: private int _capacity = 10; >
Для надання початкового значення можна вимагати, визначивши конструктор , що відповідає за налаштування цього початкового значення:
public class Container < private int _capacity; public Container(int capacity) =>_capacity = capacity; >
Починаючи з C# 12 можна визначити основний конструктор в рамках оголошення класу:
public class Container(int capacity)
Додавання параметрів в ім'я класу визначає основний конструктор. Ці параметри доступні в тілі класу, що включає його елементи. Їх можна використовувати для ініціалізації полів або в будь-якому іншому місці, де вони потрібні.
Можна також використовувати модифікатор для властивості і дозволити користувачам, що викликають, використовувати required ініціалізатор об'єктів, щоб задати початкове значення властивості:
public class Person < public required string LastName < get; set; >public required string FirstName < get; set; >>
Додавання ключового required слова вимагає, щоб користувачі, що викликають, встановлювали ці властивості як частина new виразу:
var p1 = New Person(); // Error! Потрібні properties no set var p2 = new Person() < FirstName = "Grace", LastName = "Hopper" >;
Наслідування класів
Класи повністю підтримують успадкування, фундаментальний механізм об'єктно орієнтованого програмуванняПри створенні класу можна успадковувати від іншого класу, який не визначений як sealed . Інші класи можуть успадковуватись від класу та перевизначати віртуальні методи класу. Крім того, можна реалізувати один чи кілька інтерфейсів.
При наслідуванні створюється похідний клас, тобто клас оголошується за допомогою базового класу, від якого він успадковує дані та поведінку. Базовий клас задається додаванням після імені похідного класу двокрапки та імені базового класу, як показано далі:
Public class Manager : Employee < // Employee fields, properties, methods and events inherited // New Manager fields, properties, methods and events go here. >
Якщо оголошення класу включає базовий клас, він успадковує всіх членів базового класу, крім конструкторів. Для отримання додаткових відомостей див. у розділі Спадкування.
Клас C# може безпосередньо успадковуватися тільки від одного базового класу. Однак оскільки базовий клас може успадковувати від іншого класу, клас може побічно успадковувати кілька базових класів. З іншого боку, клас може безпосередньо реалізувати кілька інтерфейсів. Для отримання додаткових відомостей див. у статті Інтерфейси.
Клас можна оголосити як abstract. Анотація клас містить абстрактні методи, які мають визначення сигнатури, але не мають реалізації. Неможливо створити екземпляр абстрактних класів. Вони можуть використовуватись лише через похідні класи, що реалізують абстрактні методи. На відміну від цього запечатаний клас не дозволяє іншим класам бути похідним від нього. Для отримання додаткових відомостей див. у статті Абстрактні та запечатані класи та члени класів.
Визначення класів можна поділити між різними вихідними файлами. Для отримання додаткових відомостей див. у розділі Розділяються класи та методи.
Специфікація мови C#
Для отримання додаткових відомостей див. у специфікації мови C#.Специфікація мови є джерелом інформації про синтаксис і використання мови C#.
Джерело цього вмісту можна знайти на GitHub, де також можна створювати та переглядати проблеми та запити на витягування. Для отримання додаткових відомостей див. у нашому посібнику для учасників.
C#. Вкладені класи. Вкладені статичні класи. Оголошення та використання вкладених класів. Приклади
Вкладені класи. Вкладені статичні класи. Оголошення та використання вкладених класів. Приклади
Пошук на інших ресурсах:
1. Поняття вкладеного класу. Загальна форма оголошення вкладеного класу
У мові C# будь-який клас у реалізації може містити оголошення іншого класу. Клас, який оголошується в межах фігурних дужок іншого класу, називається вкладеним класом.
У найпростішому випадку, загальна форма оголошення вкладеного класу має вигляд:
// клас, який містить у реалізації оголошення іншого класу class Outer < // поля та методи класу Outer // . class Inner < // поля та методи класу Inner // . > // поля та методи класу Outer // . >
- Outer - Ім'я класу, який містить у своєму тілі оголошення іншого класу з ім'ям Inner ;
- Inner – ім'я класу, що оголошується у класі Outer .
Класи Outer і Inner можуть містити різні специфікатори, які визначають доступ (private, protected, public) або інші властивості класу (static, sealed і т.д.).
⇑
2. Як виглядає загальна форма оголошення об'єкта вкладеного класу?
Об'єкт вкладеного класу можна оголосити у разі, коли вкладений клас оголошено як видимий. Це означає, що видимий вкладений клас має бути оголошений з будь-яким модифікатором доступу, крім private.
Якщо вкладений клас оголошений як не private-клас, то створення екземпляра цього класу має такий вигляд:
Outer.Inner objInner = new Outer.Inner();
- Outer – ім'я зовнішнього класу, у якому оголошується вкладений клас Inner;
- Inner – ім'я вкладеного класу, який оголошено у межах фігурних дужок класу Outer;
- objInner – ім'я об'єкта (примірника) класу, що створюється;
- Outer.Inner() – ім'я конструктора за умовчанням, який викликається створення об'єкта класу. Якщо в класі Inner реалізовані інші параметризовані конструктори, цей виклик може бути іншим.
⇑
3. Які модифікатори доступу можуть застосовуватись до вкладених класів?
До вкладених класів можуть застосовуватися такі самі модифікатори доступу як і до звичайних – неукладених класів:
- private. У цьому випадку вкладений клас є видимим у межах фігурних дужок зовнішнього класу;
- public. У цьому випадку вкладений клас доступний у межах зовнішнього класу, з екземпляра зовнішнього класу, з успадкованого класу, а також за межами складання;
- protected. У цьому випадку клас є доступним у межах зовнішнього класу а також з методів успадкованих класів;
- internal. Вкладений клас є доступним у межах складання та недоступний за межами складання;
- protected internal . Вкладений клас може бути доступний із методів зовнішнього класу, методів успадкованих класів або недоступний із методів за межами поточного складання.
Дія модифікаторів така сама як і у випадку з членами даних (методами) класу.
⇑
4. Приклад, який демонструє використання класів із різними модифікаторами доступу
У прикладі демонструється оголошення та доступ до вкладених класів, які містять різні модифікатори доступу.
Оголошується клас Outer, який містить:
- дві внутрішні public-змінні з іменами d і sd. Змінна sd оголошена як статична;
- внутрішній метод GetInner1(), що демонструє доступ до private-класу Inner1;
- вкладений клас Inner1, який оголошено як private;
- вкладений клас Inner2, який оголошено як public;
- вкладений клас Inner3, який оголошено як internal;
- вкладений клас Inner4, який оголошений як protected;
- вкладений клас Inner5, який оголошений як protected internal.
У будь-якому з вкладених класів оголошуються:
- одна нестатична змінна цілого типу;
- одна статична змінна цілого типу.
Лістинг класу Outer наступний:
// клас Outer містить 5 вкладених класів public class Outer < // внутрішні змінні класу Outer public int d; static public int sd; // доступ до private-класу Inner1 із внутрішнього методу класу public Inner1 GetInner1() < Inner1 i1 = new Inner1(); // Створити екземпляр класу Inner1 // Доступ до члена даних класу Inner1 через екземпляр класу i1.d1 = 25; // доступ до статичного члена вкладеного класу Inner1 Inner1.sd1 = 30; return i1; > // private-вкладений клас Inner1 private class Inner1 < public int d1; public static int SD1; > // public-вкладений клас Inner2 public class Inner2 < public int d2; public static int SD2; > // internal-вкладений клас Inner3 internal class Inner3 < public int d3; public static int SD3; > // protected-вкладений клас Inner4 protected class Inner4 < public int d4; public static int SD4; > // protected-internal вкладений клас Inner5 protected internal class Inner5 < public int d5; public static int SD5; > >
Використання вкладених класів у певному методі може бути таким:
// Використання вкладених класів Outer.Inner1, Outer.Inner2, Outer // 1. Оголосити об'єкт класу Outer Outer o = new Outer(); o.d = 300; // Доступ до внутрішньої змінної через екземпляр класу Outer Outer.sd = 230; // доступ до статичної змінної // 2. Немає доступу до private-класу Inner1 через екземпляр класу Outer // Outer.Inner1 i1; - помилка, клас Inner1 оголошений як private // 3. Оголосити об'єкт public-класу Inner2 Outer.Inner2 i2 = new Outer.Inner2(); i2.d2 = 440; Outer.Inner2.sd2 = 500; // доступ до статичної змінної // 4. Оголосити об'єкт internal-класу Inner3 Outer.Inner3 i3 = new Outer.Inner3(); i3.d3 = 100; Outer.Inner3.sd3 = 400; // доступ до статичної змінної // 5. Оголосити об'єкт protected-класу можна лише з успадкованих класів // Outer.Inner4 i4; - помилка // 6. Оголосити об'єкт protected-internal-класу можна Outer.Inner5 i5 = new Outer.Inner5(); i5.d5 = 200; Outer.Inner5.sd5 = -100;
Як видно з наведеного вище коду, створювати об'єкти можна тільки для класів Inner2, Inner3, Inner5. Щоб створити об'єкт protected-класу Inner4, потрібно оголосити інший клас, який успадковує клас Outer, наприклад:
// клас Outer2 є успадкованим від класу Outer class Outer2 : Outer < // У методі успадкованого класу є доступ до protected-класу Inner4 void SomeMethod() < // Створити екземпляр класу Outer.Inner4 Inner4 i4 = new Inner4(); // працює i4.d4 = 23; // доступ через екземпляр класу Inner4.sd4 = 330; // доступ до статичної змінної > >
Даний приклад також показує, що вкладений private-клас приховує доступ до оголошеної в ньому статичної змінної методів інших класів. Також приховує доступ і вкладений protected-клас.
⇑
5. Оголошення та використання статичного вкладеного класу в нестатичному. приклад
У нестатичному класі може бути оголошено статичний клас (з ключовим словом static). Це означає, що статичний клас цього класу є унікальним загальним ресурсом. Доступ до елементів статичного класу здійснюється безпосередньо на ім'я статичного класу, перед яким через точку слідує ім'я (імена) зовнішнього класу (класів).
приклад. Оголошується клас Outer, що містить оголошення вкладеного статичного (static) класу Inner. У класі Inner оголошується статична внутрішня public-змінна. Текст класу Outer наступний:
// клас Outer – нестатичний public class Outer < // внутрішні змінні класу Outer public int d; static public int sd; // Статичний вкладений клас Inner public static class Inner < // Тільки статичні змінні можуть оголошуватися в статичному класі public static int sd; // Статична внутрішня змінна класу Inner > >
Використання статичного вкладеного класу може бути таким:
// доступ до статичної змінної статичного класу Outer.Inner Outer.Inner.sd = 45; // Створити об'єкт (примірник) класу Outer Outer o = new Outer(); o.d = 30; // є доступ до змінних екземпляра класу через об'єкт Outer.sd = 102; // доступ до статичної змінної класу Outer // створювати об'єкт статичного класу не можна //Outer.Inner i = New Outer.Inner(); - Це помилка!
⇑
6. Чи можна у статичному класі оголосити нестатичний вкладений клас?
Так, можна. У статичному класі можна оголошувати нестатичні вкладені класи. Однак, у статичному класі не можна оголошувати нестатичні змінні та методи.
Докладніше про використання статичних змінних, методів, класів описується у темі:
Наприклад. Нехай оголошується статичний клас Outer, що містить реалізацію нестатичного класу Inner
// клас Outer – статичний public static class Outer < // внутрішні змінні класу Outer // Тільки статичні екземпляри можна оголошувати в статичному класі // public int d; - Помилка! static public int sd; // нестатичний вкладений клас Inner - може оголошуватися у статичному класі public class Inner < public int d; // Нестатична змінна public static int sd; // Статична внутрішня змінна класу Inner > >
Використання класів Outer та Outer.Inner може бути, наприклад, таким:
// Використання нестатичного класу в статичному класі // Доступ до статичної змінної sd класу Outer.Inner Outer.Inner.sd = 230; // Доступ до статичної змінної sd класу Outer Outer.sd = 132; // Не можна створювати екземпляр (об'єкт) статичного класу // Outer o = New Outer (); // помилка, оскільки Outer – статичний клас // Створити екземпляр вкладеного класу Outer.Inner Outer.Inner i = new Outer.Inner(); і.d = 323; // Доступ до нестатичної змінної d класу Outer.Inner
⇑
7. Приклад оголошення та використання статичного вкладеного класу в іншому статичному класі
У статичному класі може бути оголошено інший вкладений статичний клас. Тут слід пам'ятати, що у статичних класах оголошені методи та змінні повинні бути обов'язково статичними (за винятком класів). А ось класи можуть бути й нестатичні.
Нехай задані оголошення статичних класів Outer та Inner:
// клас Outer – статичний public static class Outer < // Тільки статичні екземпляри можна оголошувати в статичному класі static public int sd; // Статичний вкладений клас Inner - може оголошуватися у статичному класі public static class Inner < // тут лише статичні елементи public static int sd; // Статична внутрішня змінна класу Inner > >
Тоді використання статичних елементів цих класів може бути наступним:
// Використання статичного класу в статичному класі // Доступ до статичної змінної sd класу Outer.Inner Outer.Inner.sd = 140; // Доступ до статичної змінної sd класу Outer Outer.sd = 110; // Не можна створювати екземпляр (об'єкт) статичного класу // Outer o = New Outer (); // помилка, оскільки Outer – статичний клас
