.NET: Обзор
19 июля 2026 г.
Что такое .NET?
.NET (также dotNET) — бесплатная кроссплатформенная платформа разработки с открытым исходным кодом от компании Microsoft, которая позволяет создавать множество видов приложений.
Платформа появилась в середине 2016 года (версия .NET Core 1.0) как замена для устаревшему .NET Framework.
История .NET Framework
В 1996 году Microsoft поддержала разработку Sun Microsystems, создав собственную реализацию языка Java с некоторыми дополнениями «мимо спецификации». Таким образом появился язык Visual J++, который позиционировался как «самый новый и лучший способ создавать приложения для Windows».
Последовав своей любимой стратегии, Microsoft своими дополнениями намеренно привязала программы на Java к собственной операционной системе Windows, сделав их непригодными для запуска в системах Linux или Mac.
За отклонения от спецификации Sun Microsystems подала в суд. В конечном счете это привело к тому, что Sun Microsystems отозвала у Microsoft право использовать название Java и Visual J++, а также добилась выплаты в ~20 млн долларов (сумма на момент 2001 года).
Microsoft же в свою очередь пришлось создавать собственную платформу для разработки. В процессе было позаимствовано множество идей из Java. Например, ключевая фишка с компиляцией в промежуточный код и его последующим исполнением в виртуальной машине. Это позволяет компьютерной программе выполниться на любом устройстве, где установлена такая виртуальная машина.
Релиз первой версии .NET Framework состоялся в 2002 году.
Из чего состоит .NET?
Платформа .NET включает в себя следующие компоненты:
- Runtime — среда выполнения.
- Библиотеки — набор готового кода с базовым функционалом (работа с текстом, коллекции, операции с сетью и т. д.).
- Компилятор — сборщик C# кода (или других языков платформы) в промежуточный код для среды выполнения.
- SDK и прочие инструменты для разработки.
- Фреймворки для создания приложений — такие как ASP.NET Core или WPF.
Common Language Runtime
Общеязыковая среда выполнения (Common Language Runtime, CLR) — среда, которую предоставляет .NET. Она запускает код и предоставляет сервисы, упрощающие процесс разработки. Код, который исполняется в этой среде, называется управляемым кодом (Managed Code).
Компиляторы и средства разработки могут создавать выходные данные, которые CLR способна использовать, поскольку система типов, формат метаданных и среда выполнения определены общедоступным стандартом:
Управляемый код
Управляемый код (Managed Code) — это код, который разработан с использованием компилятора языка, нацеленного на общеязыковую среду выполнения (CLR), и выполняется под ее управлением.
Характеристики управляемого кода:
- Управление памятью: CLR управляет временем жизни объекта. За это отвечает сборщик мусора (Garbage Collector), который автоматически освобождает память, когда объект перестает быть нужным.
- Наличие метаданных: компилятор встраивает метаданные в исполняемый файл для CLR. Среда выполнения использует их для поиска и загрузки классов, размещения экземпляров в памяти, разрешения вызовов методов, генерации машинного кода, обеспечения безопасности и установки границ контекста выполнения.
- Кросс-языковая интеграция: поскольку все языки платформы используют общую систему типов и компилируются в общий промежуточный язык (Common Intermediate Language или просто CIL), то разработку основного проекта можно вести, например, на C#, а библиотеки для этого же проекта писать на VB или F#.
Важное замечание: если ваш код управляемый, то это не означает, что все данные управляемые по умолчанию.
Неуправляемые данные
Сборщик мусора (Garbage Collector, GC) — компонент в .NET, который умеет отслеживать только память в управляемой куче (Managed Heap). Однако приложение может активно взаимодействовать с операционной системой или базой данных. Ни ОС, ни БД ничего не знают про .NET и не управляются этой платформой.
Для таких данных нужно освободить память самостоятельно. Обычно неуправляемые объекты (или управляемые, которые владеют неуправляемыми) реализуют интерфейс IDisposable. Тогда у объекта можно вызвать метод void Dispose(), чтобы освободить его ресурсы.
Если мы сами написали такой объект, то реализовать этот интерфейс и написать логику освобождения ресурсов необходимо самостоятельно.
Common Language Specification
Спецификация общего языка (Common Language Specification, CLS) — набор правил, применяемых к генерируемым сборкам. Спецификация общего языка определена в части I, пунктах 7–11 стандарта ECMA-335.
Этот набор правил описывает возможности, которые являются общими для всех языков платформы .NET. Соблюдение этих правил позволяет полностью взаимодействовать с другими объектами, написанными на любом языке.
Если компонент соответствует спецификации общего языка, гарантируется, что он CLS-совместим и к нему можно обращаться из кода в сборках, написанных на любом языке программирования, поддерживающем CLS.
Чтобы спроектировать компонент, независимый от языка, нужно применять правила CLS-совместимости только к открытому интерфейсу компонента. Внутренняя реализация не обязана соответствовать спецификации.
Common Type System
Общая система типов (Common Type System, CTS) — система, которая определяет, как типы объявляются, используются и управляются в общеязыковой среде выполнения, а также является важной частью поддержки средой выполнения кросс-языковой интеграции.
CTS выполняет следующие функции:
- Устанавливает структуру, которая способствует обеспечению кросс-языковой интеграции, типобезопасности и высокопроизводительного выполнения кода.
- Предоставляет объектно-ориентированную модель, поддерживающую полную реализацию многих языков программирования.
- Определяет правила, которым должны следовать языки, что помогает гарантировать, что объекты, написанные на разных языках, могут взаимодействовать друг с другом.
- Предоставляет библиотеку, содержащую примитивные типы данных (такие как Boolean, Byte, Char, Int32 и UInt64), используемые при разработке приложений.
Типы в .NET
Все типы в .NET являются либо типами значений, либо ссылочными типами.
Типы значений (Value types) — это типы данных, объекты которых представлены собственным фактическим значением. Если экземпляр типа значения присваивается переменной, эта переменная получает новую копию значения.
Ссылочные типы (Reference types) — это типы данных, объекты которых представлены ссылкой (похожей на указатель) на фактическое значение объекта. Если ссылочный тип присваивается переменной, эта переменная ссылается (указывает) на исходное значение. Копия не создается.
CTS в .NET поддерживает пять категорий типов:
- Классы (Classes) — это ссылочный тип, который может быть непосредственно унаследован от другого класса и который неявно наследуется от
System.Object. - Структуры (Structures) — это тип значения, который неявно наследуется от
System.ValueType, который, в свою очередь, наследуется отSystem.Object. - Перечисления (Enumerations) — это тип значения, который наследуется непосредственно от
System.Enumи предоставляет альтернативные имена для значений базового примитивного типа. - Интерфейсы (Interfaces) — тип, который определяет контракт, устанавливающий отношение «может делать» (can do) или «имеет» (has a).
- Делегаты (Delegates) — это ссылочные типы, которые служат цели, аналогичной указателям на функции в C++. Они используются для обработчиков событий и функций обратного вызова в .NET.
Base Class Library
Базовая библиотека классов (Base Class Library, BCL) — набор библиотек, которые включают пространства имен System.* (и, в ограниченной степени, Microsoft.*).
.NET Standard
.NET Standard — это формальная спецификация API .NET, которые доступны в нескольких реализациях .NET.
Мотивацией создания .NET Standard было установление большей унификации в экосистеме .NET. .NET 5 и более поздние версии используют иной подход к обеспечению унификации, который устраняет необходимость в .NET Standard в большинстве сценариев.
Чтобы разделять код между .NET Framework и любой другой реализацией .NET, например .NET Core, библиотека должна быть нацелена на .NET Standard 2.0.
Новых версий .NET Standard выпускаться не будет, но .NET 5 и все последующие версии продолжат поддерживать .NET Standard 2.1 и более ранние версии.
.NET Standard имеет версионность. Каждая новая версия добавляет больше API. Когда библиотека собирается для определенной версии .NET Standard, она может выполняться в любой реализации .NET, которая поддерживает эту версию .NET Standard (или более позднюю).
Нацеливание на более высокую версию .NET Standard позволяет библиотеке использовать больше API, но означает, что она может использоваться только в более новых версиях .NET.
Компиляторы
В составе .NET есть отдельные компиляторы для разных языков, и все они генерируют промежуточный код CIL (Common Intermediate Language):
- Для C# и VB — компилятор
csc.exe(Roslyn). - Для F# — компилятор
fsc.exe(своего особого названия не имеет).
Оба этих компилятора являются частью единой экосистемы .NET и, что важно, используют одни и те же базовые механизмы и API для генерации CIL и сборок, что и обеспечивает их совместимость.
Тем не менее Roslyn не используется для компиляции всех официальных языков платформы. На то есть несколько причин:
- F# разрабатывает другая команда, у которой есть стремление сделать на F# все. И компилятор этого языка написан на нем же, тогда как Roslyn написан на C#.
- F# является языком совершенно иной парадигмы и требует особого отношения при компиляции. Например, компилятор F# умеет выполнять оптимизацию хвостовой рекурсии (в отличие от Roslyn).
Компиляция в CIL
Общий промежуточный язык (Common Intermediate Language, CIL) — это набор инструкций, не зависящих от конкретного процессора, которые могут быть эффективно преобразованы в машинный код. CIL включает инструкции для загрузки, сохранения, инициализации и вызова методов объектов, а также инструкции для арифметических и логических операций, управления потоком выполнения, прямого доступа к памяти, обработки исключений и других операций.
Пример программы “Hello World”, написанной на чистом CIL:
// Объявление сборки (Assembly)
.assembly extern mscorlib
{
.ver 4:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89) // Стандартный токен для mscorlib
}
.assembly HelloWorld
{
.ver 1:0:0:0
}
.module HelloWorld.exe
// Определение класса Program
.class public auto ansi Program extends [mscorlib]System.Object
{
// Метод Main - точка входа
.method public static void Main() cil managed
{
.entrypoint
.maxstack 1
// Загружаем строку "Hello, World!" в стек
ldstr "Hello, World!"
// Вызываем метод Console.WriteLine(string)
call void [mscorlib]System.Console::WriteLine(string)
// Возвращаемся из метода
ret
}
}
Перед выполнением код CIL должен быть преобразован в код, специфичный для конкретного процессора. Обычно это делает JIT-компилятор (Just-In-Time).
Поскольку среда CLR предоставляет один или несколько JIT-компилятором для каждой поддерживаемой компьютерной архитектуры, один и тот же набор CIL может быть скомпилирован JIT и выполняться на любой поддерживаемой архитектуре.
Когда компилятор создает CIL, он также генерирует метаданные, которые среда выполнения находит и извлекает из исполняемого файла по мере необходимости во время выполнения.
Компиляция CIL в машинный код
Перед тем как запустить общий промежуточный язык (CIL), он должен быть скомпилирован для общеязыковой среды выполнения (CLR) в машинный код, соответствующий архитектуре целевой машины. .NET предоставляет два способа выполнения этого преобразования:
- JIT-компилятор (Just-In-Time).
- Ngen.exe.
Компиляция JIT-компилятором
JIT-компиляция преобразует CIL в машинный код по требованию во время выполнения приложения, когда содержимое сборки загружается и выполняется.
Загрузчик создает и прикрепляет заглушку к каждому методу в типе, когда тип загружается и инициализируется. Когда метод вызывается в первый раз, заглушка передает управление JIT-компилятору, который преобразует CIL этого метода в машинный код и изменяет заглушку так, чтобы она указывала непосредственно на сгенерированный машинный код.
Поскольку общеязыковая среда выполнения предоставляет JIT-компилятор для каждой поддерживаемой архитектуры ЦП, разработчики могут создать набор сборок на CIL, которые могут быть скомпилированы JIT-компилятором и запущены на разных компьютерах с различными архитектурами машин.
Однако если управляемый код вызывает платформозависимые нативные API или платформозависимую библиотеку классов, он будет работать только в этой операционной системе.
Генерация кода во время установки с помощью NGen.exe
Поскольку JIT-компилятор преобразует CIL сборки в машинный код, когда вызываются отдельные методы, определенные в этой сборке, это негативно влияет на производительность во время выполнения. В большинстве случаев это снижение производительности приемлемо.
Что более важно, код, сгенерированный JIT-компилятором, привязан к процессу, который инициировал компиляцию. Он не может быть разделен между несколькими процессами.
Чтобы обеспечить возможность совместного использования сгенерированного кода между несколькими запусками приложения или между несколькими процессами, использующими общий набор сборок, общеязыковая среда выполнения поддерживает режим упреждающей (ahead-of-time) компиляции.
Этот режим упреждающей компиляции использует Ngen.exe (генератор машинных образов) для преобразования сборок CIL в машинный код, подобно тому, как это делает JIT-компилятор. Однако работа Ngen.exe отличается от работы JIT-компилятора в трех аспектах:
- Преобразование из CIL в машинный код происходит до запуска приложения, а не во время его работы.
- Компилируется целая сборка за раз, а не по одному методу.
- Сгенерированный код сохраняется в кэше машинных образов в виде файла на диске.
.NET SDK
.NET SDK — это набор библиотек и инструментов, которые разработчики используют для создания .NET-приложений и библиотек. Он содержит следующие компоненты:
- .NET CLI (интерфейс командной строки).
- Среда выполнения .NET и библиотеки.
- Драйвер dotnet.
.NET CLI
.NET CLI (Command-Line Interface) — это кроссплатформенный набор инструментов для разработки, сборки, запуска и публикации .NET-приложений. Он входит в состав .NET SDK.
Структура команды CLI состоит из драйвера (dotnet), самой команды и, возможно, аргументов и параметров (опций) команды.
Пример создания консольного приложения в текущей директории и его последующего запуска:
dotnet new console
dotnet run