Підрозділ 8.8
Анонімні типи
Розглядає анонімні типи, ініціалізатори об'єктів, проекційні ініціалізатори, read-only властивості та масиви анонімних об'єктів.
8.8. Анонімні типи
Іноді нам потрібен об'єкт лише для одного конкретного місця в коді: щоб тимчасово зберегти кілька пов'язаних значень, передати у функцію виведення або зібрати результат вибірки. Створювати повноцінний клас заради одноразового використання — надлишок. Для таких ситуацій у C# існують анонімні типи (anonymous types): об'єкти без явно оголошеного класу, де компілятор сам генерує внутрішній тип на льоту.
Анонімний тип визначається через var і ініціалізатор об'єкта без вказівки класу:
var patient = new { Name = "Іван Петренко", Age = 45 };
Console.WriteLine(patient.Name); // Іван Петренко
Console.WriteLine(patient.Age); // 45Тут немає жодного класу Patient — компілятор сам порозбирається з типом.
Що насправді генерує компілятор
Анонімний тип — це не магія. Компілятор генерує внутрішній клас з автоматичним іменем і підставляє його скрізь, де використовується var. Цей клас має конкретну структуру:

sealed — анонімний тип не можна розширити спадкуванням.
readonly властивості — усі властивості анонімного типу доступні тільки для читання. Після ініціалізації змінити значення неможливо. Це свідомий дизайн: анонімні типи — immutable value containers.
Автоматичне ім'я — компілятор дає класу ім'я виду <>f__AnonymousType0. Кутові дужки <> — заборонений символ в ідентифікаторах C#. Це навмисно: програміст не може написати це ім'я у коді, що гарантує — ніхто не зможе зберегти посилання на тип у явно типізованій змінній.
Equals і GetHashCode — компілятор перевизначає обидва методи на основі структурного порівняння. Два анонімних об'єкти з однаковими іменами властивостей, типами та значеннями — рівні.
ToString — повертає рядок виду { Name = Іван Петренко, Age = 45 } — зручно для діагностики та виведення.
Основний синтаксис та ініціалізатори з проекцією
Крім явного присвоєння значень, анонімні типи підтримують ініціалізатори з проекцією (projection initializers): можна передати ідентифікатор або вираз, і ім'я змінної стане ім'ям властивості:
Рівність анонімних типів
Як вже зазначалось, компілятор генерує структурну рівність. Два об'єкти анонімного типу рівні, якщо мають однакові назви властивостей, однакові типи — і однакові значення. Більше того: якщо у програмі визначено кілька анонімних об'єктів з однаковим набором, порядком та типами властивостей, компілятор створить для них одне визначення класу:
Умова спільного визначення: однаковий набір властивостей, однаковий порядок, однакові типи. Якщо хоч щось відрізняється — компілятор створить окремий тип.
Масиви анонімних типів
Масив анонімних типів визначається через new[] — компілятор виведе тип елементів із першого ініціалізатора:
var patients = new[]
{
new { Name = "Іван Петренко", Age = 45, Diagnosis = "Гіпертонія" },
new { Name = "Марія Коваль", Age = 32, Diagnosis = "Діабет" },
new { Name = "Олег Сидоренко",Age = 58, Diagnosis = "Аритмія" },
};
foreach (var p in patients)
Console.WriteLine($"{p.Name} ({p.Age} р.) — {p.Diagnosis}");Усі елементи масиву повинні мати однаковий набір властивостей. Якщо хоч один відрізняється — помилка компіляції.
Область видимості та обмеження
Анонімний тип обмежений своєю областю видимості. Через var ми можемо зберегти об'єкт у локальній змінній, але не можна явно вказати тип анонімного об'єкта в сигнатурі методу, у полі класу чи в параметрі. Якщо потрібно передати анонімний об'єкт в інший метод, є лише два способи: через object (з втратою типізації) або через dynamic (з відкладеною перевіркою типів).
// НЕ можна:
// Patient GetAnonymous() => new { Name = "Іван", Age = 45 }; // помилка
// Можна через object (але доведеться кастити або використовувати рефлексію):
object obj = new { Name = "Іван", Age = 45 };
// Або через dynamic (повільно, без підтримки IDE):
dynamic dyn = new { Name = "Іван", Age = 45 };
Console.WriteLine(dyn.Name);Саме ця обмеженість і визначає, де анонімні типи доречні: у межах одного методу, для тимчасового зберігання або перетворення даних.
Readonly. Після створення об'єкта змінити його властивості не можна:
var patient = new { Name = "Іван", Age = 45 };
patient.Age = 46; // помилка компіляції — властивість readonlyЯкщо потрібна змінювана структура — краще використовувати кортеж (8.9) або звичайний клас.
Зв'язок з LINQ
Анонімні типи є основним механізмом для проекцій у LINQ. Оператор select часто повертає не вихідний об'єкт, а лише потрібний набір полів — і саме тут анонімні типи незамінні:
// LINQ-вибірка лише потрібних полів (буде детально в розділі 13):
var summaries = patients.Select(p => new
{
p.Name,
p.Diagnosis,
IsRisk = p.Age > 50
});Кожен елемент summaries — це анонімний об'єкт з трьома полями. Клас для цього не потрібен — і не потрібен буде ніколи, бо ці об'єкти живуть лише в межах одного запиту.
Коли анонімний тип, а коли щось інше
| Потреба | Рекомендація |
|---|---|
| Тимчасова проекція в межах методу / LINQ-запиту | Анонімний тип |
| Передача між методами з типізацією | Кортеж (string Name, int Age) (розділ 8.9) |
| Багаторазово використовуваний іменований тип | record (розділ 8.10) або клас |
| Потрібна мутабельність (зміна після створення) | Клас зі звичайними властивостями |
| Потрібне успадкування | Клас |
Анонімні типи добре підходять для «одноразових» проекцій, де ім'я типу взагалі не має значення. Якщо ж структура даних потрібна більше ніж в одному місці коду — варто оголосити явний тип.