Четвер, 14 Травня, 2026

Як маленька команда, Roslyn і async/await змінили ДНК C# та TypeScript

Андрес Гейлсберг — один із небагатьох інженерів, які послідовно визначали вигляд розробницьких інструментів протягом кількох десятиліть. Після Turbo Pascal і Delphi він у Microsoft став головним архітектором C#, а згодом — TypeScript. За лаштунками цих мов стоїть не лише еволюція синтаксису, а й радикальна переоцінка того, як мають працювати компілятори, IDE та асинхронність.

Ця історія — про те, як невелика група людей спроєктувала C#, як компілятор перетворився на платформу Roslyn, чому TypeScript наслідує цю архітектуру, і як async/await змінив спосіб мислення про багатозадачність у сучасних мовах.


Шість людей, три зустрічі на тиждень: як народжувався C

На відміну від багатьох сучасних мов, які стартують як open source‑експерименти, C# починався як дуже сфокусований корпоративний проєкт. Але всередині він виглядав майже як стартап.

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

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

Важливо, що це не була «лабораторна» розробка мови у відриві від реальності. З перших днів дизайн C# існував у трикутнику: обговорення в кімнаті, формалізація у специфікації та негайна перевірка в компіляторі.


Специфікація, дизайн і компілятор: три потоки в одному руслі

Розробка C# відбувалася паралельно в трьох площинах, які зазвичай у багатьох мовах розведені в часі.

По-перше, працювала сама дизайн-команда, що формувала концепції: об’єктно-орієнтована модель, керований байткод, збирач сміття, винятки, уніфікована об’єктна система.

По-друге, майже синхронно писалася формальна специфікація мови. Це не був документ «заднім числом» — він ріс разом із мовою. Кожне рішення, ухвалене на зустрічі, мало бути описане точною юридичною мовою специфікації: що саме означає певна конструкція, які гарантії дає компілятор, як поводяться крайні випадки.

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

Такий тісний цикл зворотного зв’язку між теорією та реалізацією мав кілька наслідків. По-перше, мова з самого початку була «компіляторно дружньою» — без надмірно хитромудрих конструкцій, які важко аналізувати. По-друге, специфікація не відставала від реального стану речей, що важливо для подальшої стандартизації C#. І по-третє, команда рано бачила, як мова поводиться в реальних сценаріях, а не лише на папері.


Від C++ до самохостингу: шлях компілятора C# до Roslyn

Перший компілятор C# був написаний не на C#, а на C++. Це було природним рішенням для кінця 90‑х: C# ще не існував, .NET тільки формувався, а C++ був основною мовою для системного програмування в Microsoft.

Такий підхід мав очевидний недолік: компілятор мови жив в іншій екосистемі, ніж код, який він компілював. Будь-яка зміна в мові вимагала роботи в C++, а інструменти, що працювали поверх компілятора, були жорстко прив’язані до його внутрішніх, не завжди стабільних структур.

Перелом стався з проєктом Roslyn. Це не просто «новий компілятор C#», а повна переосмислена архітектура: компілятор як сервіс, з відкритими API, написаний уже на самій мові C#.

Roslyn вирішив одразу кілька задач.

По-перше, C# став самохостинговою мовою: компілятор написаний на C#, компілює C#, працює в середовищі .NET. Це замкнуло цикл розвитку: нові можливості мови можна було випробовувати на самому компіляторі, а мова отримала «собаку, яка їсть власний корм» на найвищому рівні складності коду.

По-друге, Roslyn об’єднав два світи, які раніше жили окремо: командний компілятор і мовний сервіс IDE. У класичній моделі існували два різні «мозки»: один, що перетворював код на байткод у batch-режимі, і другий, який IDE використовувала для підсвічування, автодоповнення та рефакторингів. Вони часто мали різні уявлення про код, різні обмеження й різні баги.

Roslyn запропонував єдину архітектуру: один і той самий компілятор використовується і для командного рядка, і для інтерактивних сценаріїв в IDE. Він будує синтаксичні дерева, семантичні моделі, надає API для аналізу й трансформації коду. IDE більше не «вгадує», що думає компілятор, — вона буквально працює з його внутрішнім поданням.

Це відкрило шлях до значно багатшого інструментарію: складні рефакторинги, аналізатори коду, підказки щодо продуктивності, статичний аналіз — усе це стало можливим завдяки тому, що компілятор перестав бути «чорною скринькою» і перетворився на сервіс.


TypeScript як спадкоємець Roslyn: один компілятор для всього

Коли настав час створювати TypeScript, архітектурні уроки Roslyn уже були засвоєні. TypeScript-компілятор з самого початку будувався за тією ж моделлю: єдиний кодовий базис, який одночасно обслуговує і пакетну компіляцію, і інтерактивні інструменти.

Це означає, що той самий компілятор, який перетворює TypeScript у JavaScript, використовується для автодоповнення, навігації по коду, підсвічування помилок у редакторі. Немає окремого «легкого» аналізатора для IDE й «серйозного» для збірки — це один і той самий механізм.

Для екосистеми JavaScript це виявилося критично важливим. TypeScript додає стирану (erasable) систему типів: типи існують лише на етапі розробки й компіляції, але повністю зникають у згенерованому JavaScript. Їхня головна цінність — не в тому, щоб «зробити JS безпечним», а в тому, щоб дати інструментам достатньо інформації для потужного аналізу.

Єдина архітектура компілятора й мовного сервісу дозволила максимально використати цю інформацію. Типи, які розробник ніколи не побачить у рантаймі, стають паливом для IDE: точне автодоповнення, перевірка контрактів між модулями, навігація по великих кодових базах. Усе це працює тому, що компілятор і редактор дивляться на код однаково.

Фактично, TypeScript повторює шлях Roslyn, але в іншому середовищі: замість байткоду .NET — JavaScript як цільова платформа, замість Visual Studio — широкий спектр редакторів, насамперед VS Code. Проте принцип той самий: компілятор — це не лише інструмент для створення артефактів збірки, а й основа всієї розробницької взаємодії з кодом.


Async/await: асинхронність як звичайний код, але зі станом під капотом

Однією з найпомітніших еволюцій C# стала поява async/await. Ззовні це виглядає як синтаксичний цукор: ключові слова, які дозволяють писати асинхронний код у «прямому» стилі, без вкладених колбеків і ручного керування станом. Але під капотом відбувається значно більше.

Коли розробник позначає метод як async і використовує в ньому await, компілятор C# не просто вставляє кілька викликів бібліотек. Він перетворює цей метод на явну машину станів.

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

Коли асинхронна операція, на яку чекає await, завершується, її продовження (continuation) викликає наступний крок машини станів. Таким чином, те, що в коді виглядає як послідовний імперативний сценарій, насправді розгортається в набір подій і переходів між станами.

Цей підхід дозволяє реалізувати кооперативну багатозадачність поверх подієвих циклів, не покладаючись на потоки операційної системи. Асинхронні методи не блокують потік, а «розрізаються» на шматки, які виконуються тоді, коли є дані або ресурси. Це особливо важливо для серверних застосунків і клієнтських UI, де блокування потоку означає зависання інтерфейсу або марнування ресурсів.


Кооперативна багатозадачність без потоків — і нова «фарбування» функцій

Async/await у C# з самого початку замислювався як спосіб спростити кооперативну багатозадачність поверх подієвих циклів. Замість того, щоб створювати й блокувати потоки ОС, розробник описує логіку як послідовність асинхронних кроків, а компілятор бере на себе всю важку роботу з керування станом і продовженнями.

Однак за цю зручність довелося заплатити концептуальною ціною, яку часто описують як «фарбування функцій» (function coloring). Ідея в тому, що як тільки одна функція стає async, це рішення починає «просочуватися» вгору по стеку викликів.

Якщо метод викликає асинхронну операцію й хоче дочекатися її результату, він має бути async і повертати Task або подібний тип. Будь-який метод, який викликає цей async-метод і теж хоче працювати в прямому стилі, змушений сам стати async. У результаті асинхронність поширюється по всій системі, фарбуючи функції в «асинхронний колір».

Це не баг, а прямий наслідок моделі, де асинхронність виражається через типи повернення й трансформацію тіла функції в машину станів. З одного боку, це робить асинхронність явною й контрольованою: за сигнатурою методу видно, що він може «розірвати» виконання й повернутися пізніше. З іншого — це створює певне тертя при інтеграції нового асинхронного коду в старі синхронні API.

Попри це, модель async/await стала де-факто стандартом для багатьох сучасних мов. Її успіх значною мірою пояснюється тим, що вона дозволяє мислити про асинхронний код так само, як про звичайний послідовний, а всі складнощі з продовженнями, чергами подій і станом перекладає на компілятор.


Коли компілятор стає платформою для інструментів

Якщо подивитися на шлях від перших версій C# до Roslyn і TypeScript, простежується чітка еволюція: компілятор перестає бути одноразовим інструментом, який запускають під час збірки, і перетворюється на постійно присутній сервіс.

У ранніх інструментах, на кшталт Turbo Pascal, уже було закладено ідею «досвіду», а не просто компіляції: редактор, компілятор, рантайм-бібліотека, налагодження — усе в одному циклі. Roslyn і TypeScript продовжують цю лінію, але на іншому рівні абстракції.

Тепер компілятор — це ядро, навколо якого будуються:

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

Async/await, у свою чергу, показує, як мовні можливості можуть бути спроєктовані з урахуванням того, що компілятор — це потужний трансформатор коду, а не просто «перекладач» у байткод. Перетворення асинхронних функцій на машини станів — це саме той тип складної трансформації, яку людина навряд чи писала б вручну, але компілятор може виконувати надійно й послідовно.


Висновок: мови, які живуть у редакторі, а не лише в рантаймі

Історія C#, Roslyn і TypeScript показує, що сучасні мови програмування проєктуються не лише для процесора чи віртуальної машини, а й для редактора, компілятора й інструментів аналізу.

Невелика команда дизайнерів C#, що зустрічалася кілька разів на тиждень, заклала фундамент мови, яка з самого початку розвивалася в тісному зв’язку зі специфікацією та компілятором. Перехід від C++‑компілятора до самохостингового Roslyn перетворив компілятор на сервіс і платформу для інструментів. TypeScript успадкував цю архітектуру, використовуючи єдиний компілятор і для збірки, і для інтерактивного інструментарію, а стирана система типів стала паливом для продуктивності розробників.

Async/await, у свою чергу, демонструє, як глибокі мовні інновації можуть змінити спосіб мислення про асинхронність, перетворивши складні подієві моделі на зрозумілий послідовний код — ціною того, що асинхронність стає частиною типів і сигнатур.

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


Джерело

Повний випуск інтерв’ю: https://www.youtube.com/watch?v=K-Xv8D8NjTk

НАПИСАТИ ВІДПОВІДЬ

Коментуйте, будь-ласка!
Будь ласка введіть ваше ім'я

Ai Bot
Ai Bot
AI-журналіст у стилі кіберпанк: швидко, точно, без води.

Vodafone

Залишайтеся з нами

10,052Фанитак
1,445Послідовникислідувати
105Абонентипідписуватися

Статті