C# / .NET — платформа от Microsoft

C# — современный объектно-ориентированный язык от Microsoft, а .NET — платформа, на которой он работает: на Windows, macOS и Linux. На C# пишут веб-сервисы на ASP.NET Core, десктопные приложения, игры в Unity и инструменты для разработки. Стабильный выбор для карьеры в enterprise.

Уровень: средний 18 разделов 3–4 часа

1. Что такое .NET и C#

.NET — это среда исполнения и библиотека классов. Программа на C# компилируется в промежуточный код (IL), который выполняет виртуальная машина .NET с сборщиком мусора. Современная версия — .NET 8+, единая для всех платформ.

ОбластьИнструмент
Веб-APIASP.NET Core Web API
Веб-сайтыASP.NET Core MVC / Razor Pages, Blazor
ДесктопWPF, WinForms, Avalonia
ИгрыUnity (использует C#)
Мобильные.NET MAUI
C# требует больше дисциплины, чем Python или PHP: строгая типизация, классы и пространства имён. Зато типичные ошибки ловятся на этапе компиляции, а не на живом сайте.

2. Установка SDK и первая программа

Установи .NET SDK с официального сайта dotnet.microsoft.com. SDK включает компилятор, runtime и инструмент командной строки dotnet.

# проверка установки
dotnet --version       # 8.0.x

# создание консольного приложения
dotnet new console -n HelloWorld
cd HelloWorld
dotnet run

Команда dotnet new console создаёт файл Program.cs:

using System; // современные шаблоны подключают нужное неявно

Console.WriteLine("Привет, мир!");

Запуск dotnet run компилирует и выполняет программу. Файл проекта имеет расширение .csproj.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
Visual Studio (Windows) и JetBrains Rider дают IDE с отладчиком. Для быстрого старта хватит VS Code с расширением C# Dev Kit: dotnet run работает и из терминала.
LTS-версии (например, .NET 8 LTS) поддерживаются долго и рекомендованы для новых проектов. Версии без LTS быстрее устаревают — портирование потом стоит сил.
# полезные команды
dotnet build            # только сборка
dotnet test             # запуск тестов
dotnet publish -c Release  # сборка для деплоя

3. Типы и переменные

C# строго типизирован. Переменная объявляется с типом, и компилятор не даст смешать несовместимое.

string name = "Аня";
int age = 25;
double height = 1.72;
bool active = true;
decimal price = 4990.50m;   // m — префикс для decimal

// неявная типизация (тип выводится)
var city = "Москва";
var year = 2026;

// интерполяция строк
Console.WriteLine($"Привет, {name}! Тебе {age} лет.");

// var нельзя использовать без присваивания
// var x; // ошибка компиляции

Основные типы

ТипРазмерПример
int4 байта42
long8 байт1000000000L
double8 байт3.14
decimal16 байт199.95m
bool1 битtrue
char2 байта'Ж'
string—"текст"
// неизменяемое значение (объявляется в верхнем регистре)
const double Pi = 3.14159;

// преобразование типов: явное и через Parse
int x = 7;
double d = x;             // неявное расширение
int y = (int)d;           // явное сужение (отбросит дробь)
int n = int.Parse("42");  // строка → число (риск: исключение)

// безопасная попытка
bool ok = int.TryParse("не число", out int result); // ok = false
Для денежных расчётов используй decimal, а не double: двоичная плавающая точка даёт ошибки округления (0.1 + 0.2 ≠ 0.3). Разница критична для цен, налогов и остатков.
C# различает значимые типы (int, double, bool — хранят значение) и ссылочные (string, классы — хранят ссылку). Value-типы нельзя сделать null без ?:, например int? a = null.

4. Условия и циклы

Конструкции if-else, switch, for, while и foreach знакомы каждому программисту. Добавим современный pattern matching.

int score = 85;

if (score >= 90)
{
    Console.WriteLine("Отлично");
}
else if (score >= 60)
{
    Console.WriteLine("Хорошо");
}
else
{
    Console.WriteLine("Подтянись");
}

// switch
string day = "saturday";
switch (day)
{
    case "saturday":
    case "sunday":
        Console.WriteLine("Выходной");
        break;
    default:
        Console.WriteLine("Рабочий день");
        break;
}
// switch-выражение (свойство C# 8+)
string result = score switch
{
    >= 90 => "Отлично",
    >= 60 => "Хорошо",
    _     => "Подтянись"
};

// циклы
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

int n = 10;
while (n > 0)
{
    n--;
}

string[] names = { "Аня", "Боря", "Вера" };
foreach (var name in names)
{
    Console.WriteLine(name);
}

// break / continue для управления циклом
for (int i = 1; i <= 10; i++)
{
    if (i % 2 == 0) continue;
    if (i > 7) break;
    Console.WriteLine(i);  // 1 3 5 7
}
Фигурные скобки в if не обязательны при одном операторе, но всегда оставляй их: они избавляют от ошибок при добавлении строк позже. Большинство стайлгайдов требуют блоки.

5. Методы

Методы — именованные блоки кода с входными параметрами и результатом. Используй их, чтобы не повторять одну и ту же логику.

// метод возвращает int и принимает два int
static int Add(int a, int b)
{
    return a + b;
}

// без возвращаемого значения — void
static void Greet(string name, string prefix = "Привет")  // параметр по умолчанию
{
    Console.WriteLine($"{prefix}, {name}!");
}

// возврат нескольких значений (кортеж)
static (int sum, int count) Stats(int[] nums)
{
    int sum = 0;
    foreach (var n in nums) sum += n;
    return (sum, nums.Length);
}

Console.WriteLine(Add(2, 3));          // 5
Greet("Аня");                          // Привет, Аня!
Greet("Боря", "Здравствуйте");         // Здравствуйте, Боря!

var (sum, count) = Stats(new[] { 1, 2, 3 });
Console.WriteLine($"{sum} / {count}"); // 6 / 3
// перегрузка: одинаковое имя, разные параметры
static string Format(int value) => $"число {value}";
static string Format(string value) => "строка: " + value;

// математические помощники
static int Factorial(int n) => n <= 1 ? 1 : n * Factorial(n - 1);

Модификаторы параметров

МодификаторЧто делает
refпередаёт значение по ссылке (обязательна инициализация)
outвыходное значение, инициализируется методом
paramsпеременное число аргументов
(по умолчанию)передача копии значения
static void Increment(ref int value) => value++;

int x = 5;
Increment(ref x);   // x становится 6

static void Split(string s, out string head, out string tail)
{
    head = s.Length > 1 ? s.Substring(0, 1) : "";
    tail = s.Length > 1 ? s.Substring(1) : s;
}
Разделяй методы на маленькие функции с одним назначением («single responsibility»). Код с методами до 15 строк легче читать, покрывать тестами и менять.

6. Классы и ООП

Класс объединяет данные (поля, свойства) и поведение (методы). Объекты создаются через new. C# поддерживает три кита ООП: инкапсуляцию, наследование и полиморфизм.

public class Product
{
    // автосвойства — удобные «обёртки» над полями
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int DiscoutPercent { get; set; }

    // вычисляемое свойство (только чтение)
    public decimal DiscountedPrice => Price * (1m - DiscoutPercent / 100m);

    // конструктор
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    // метод
    public string Describe()
    {
        return $"{Name} ({Price:C})";
    }
}

var shoes = new Product("Кроссовки", 4990m);
Console.WriteLine(shoes.Describe());
Console.WriteLine(shoes.DiscountedPrice);
// наследование и virtual/override
public abstract class Shape          // абстрактный класс
{
    public abstract double Area();   // без реализации
}

public class Circle : Shape
{
    public double Radius { get; set; }

    public override double Area() => Math.PI * Radius * Radius;
}

var circle = new Circle { Radius = 2 };
Console.WriteLine($"{circle.Area():F2}");   // 12.57

Модификаторы доступа

МодификаторВидимость
publicдоступен везде
privateтолько внутри класса
protectedкласс и наследники
internalв пределах сборки
Используй auto-свойства { get; set; } вместо открытых полей: их позже можно снабдить проверками, не ломая вызывающий код. Поля делай private.
Избегай глубоких цепочек наследования. Предпочитай композицию: один класс содержит другой, а не наследует от него. Об этом же говорит принцип SOLID.

7. Коллекции и LINQ

Коллекции хранят группы объектов. Основные: List, Dictionary, HashSet. LINQ — язык запросов к коллекциям: фильтрация, сортировка, проекция и агрегация одной цепочкой.

// List — динамический массив
var products = new List<Product>
{
    new Product("Кроссовки", 4990m),
    new Product("Шапка", 990m),
    new Product("Куртка", 7990m),
};

products.Add(new Product("Носки", 300m));
products.RemoveAll(p => p.Price < 500);

// Dictionary — пары «ключ => значение»
var stock = new Dictionary<string, int>
{
    ["Кроссовки"] = 10,
    ["Шапка"] = 25,
};
stock["Куртка"] = 5;

Console.WriteLine(stock["Шапка"]);   // 25
// LINQ — работа с коллекциями (нужен using System.Linq)
var cheap = products
    .Where(p => p.Price < 5000)
    .OrderBy(p => p.Price)
    .Select(p => p.Name)
    .ToList();

foreach (var name in cheap)
{
    Console.WriteLine(name);
}

// множество полезных операций
bool anyCheap = products.Any(p => p.Price < 1000);
int count = products.Count();
decimal total = products.Sum(p => p.Price);
decimal avg = products.Average(p => p.Price);
var first = products.FirstOrDefault();   // null, если список пуст
var max = products.Max(p => p.Price);

Основные операторы LINQ

ОператорНазначение
Whereфильтрация по условию
OrderBy / OrderByDescendingсортировка
Selectпроекция (преобразование)
First / FirstOrDefaultпервый элемент (или null)
Any / Allпроверка условия
Sum / Average / Max / Minагрегация
GroupByгруппировка
LINQ записывается либо методом (Where(...)), либо синтаксисом запроса. Методы удобнее и применяются чаще. Запросы «ленивые»: выполняются при реальном переборе — держи это в уме при работе с базами.

8. Работа с файлами

Работа с файловой системой идёт через пространство имён System.IO. Методы сетились на удобные обёртки: File, Directory, Path.

using System.IO;

// запись и чтение текста
File.WriteAllText("log.txt", "Начало работы\n");
File.AppendAllText("log.txt", $"Событие в {DateTime.Now}\n");

string content = File.ReadAllText("log.txt");
Console.WriteLine(content);

// проверки
bool exists = File.Exists("log.txt");
File.Copy("log.txt", "log_backup.txt", overwrite: true);
File.Delete("log_backup.txt");

// работа с папками
Directory.CreateDirectory("data/2026");
string[] files = Directory.GetFiles("data");
foreach (string file in files)
{
    Console.WriteLine(file);
}
// работа с JSON (System.Text.Json)
using System.Text.Json;

var order = new Order { Id = 1, Total = 4990m };
string json = JsonSerializer.Serialize(order);
File.WriteAllText("order.json", json);

Order loaded = JsonSerializer.Deserialize<Order>(File.ReadAllText("order.json"));

public class Order
{
    public int Id { get; set; }
    public decimal Total { get; set; }
}
Файловые операции способны бросить исключения: файла нет, занят, нет прав. Всегда оборачивай критичные операции в try-catch и логируй ошибку, а не давай приложению упасть молча.
try
{
    var text = File.ReadAllText("config.json");
    Console.WriteLine(text);
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("Конфиг не найден: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Ошибка: " + ex.Message);
}
Используй using для освобождения ресурсов (файлы, потоки, сеть). В современном C# проще using-объявление: using var reader = new StreamReader("file.txt"); — ресурс закроется автоматически на выходе из блока.

9. ASP.NET Core API

ASP.NET Core — фреймворк для веб-приложений и API. Минимальный API: пара файлов, и REST-сервис готов.

dotnet new webapi -n ShopApi
cd ShopApi
dotnet run
// Program.cs — минимальный API
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var products = new List<Product>
{
    new(1, "Кроссовки", 4990m),
    new(2, "Шапка", 990m),
};

app.MapGet("/products", () => products);

app.MapGet("/products/{id}", (int id) =>
    products.FirstOrDefault(p => p.Id == id) is { } product
        ? Results.Ok(product)
        : Results.NotFound());

app.MapPost("/products", (Product input) =>
{
    var id = products.Max(p => p.Id) + 1;
    products.Add(input with { Id = id });
    return Results.Created($"/products/{id}", input);
});

app.Run();

record Product(int Id, string Name, decimal Price);
# проверка
curl http://localhost:5XXX/products

Типовые команды шаблона

Тип ответаМетодHTTP-код
OkResults.Ok(value)200
CreatedResults.Created(url, value)201
Not FoundResults.NotFound()404
Bad RequestResults.BadRequest()400
No ContentResults.NoContent()204
Обрати внимание на record — неизменяемые типы с автоматическим сравнением: идеальны для DTO и передачи данных между слоями.
В реальном проекте данные хранятся в базе. EF Core превращает CLR-классы в таблицы, а LINQ-запросы — в SQL. Схему применения сутьcourse показывают отдельные уроки по SQL и базам данных.

10. NuGet и Docker

NuGet — менеджер пакетов .NET: библиотеки ставятся одной командой. Docker упаковывает приложение со всей средой, делая деплой одинаковым на любом сервере.

# установка пакета
dotnet add package Dapper
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet restore     # восстановление зависимостей проекта
// какой файл добавился в .csproj:
<PackageReference Include="Dapper" Version="2.1.35" />

Образ для .NET создаётся многостадийной сборкой: сначала full SDK компилирует, затем runtime-образ несёт готовый проект.

# Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY . .
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/out .
EXPOSE 8080
ENTRYPOINT ["dotnet", "ShopApi.dll"]
docker build -t shop-api .
docker run -p 8080:8080 shop-api

Шпаргалка по сборке проекта

КомандаНазначение
dotnet restoreзагрузить зависимости
dotnet buildсобрать без зависимостей
dotnet testзапустить unit-тесты
dotnet publish -c Releaseподготовить к деплою
dotnet runзапустить локально
Секреты (строки подключения, ключи) храни в конфигурации и переменных окружения — appsettings.json и env перекрывают друг друга. Никогда не клади пароли в репозиторий.
Docker избавляет от проблемы «у меня работает». Погружение в контейнеры, оркестрацию и CI — в курсах Docker, Kubernetes и GitHub Actions.

11. Асинхронность: async/await

Асинхронный код не блокирует поток выполнения, пока ждёт сеть или диск. Ключевые правила async/await:

public async Task<string> LoadAsync() {
    using var client = new HttpClient();
    return await client.GetStringAsync("https://api.example.com/data");
}
Параллельные независимые запросы запускай через Task.WhenAll — это сокращает общее время ожидания без распараллеливания потоков.
Не смешивай async и блокирующие вызовы: sync-over-async (например, .Result в коде БД) приводит к тупикам и деградации пула потоков.

12. Dependency Injection

DI-контейнер веб-приложений собирает зависимости объектов автоматически. Типичная регистрация в Program.cs:

builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddTransient<IEmailer, SmtpEmailer>();
builder.Services.AddSingleton<ICache, RedisCache>();
public class OrderService {
    public OrderService(IOrderRepository repo, ILogger<OrderService> log) {
        _repo = repo; _log = log;   // зависимости приходят «извне»
    }
}
Тестировать классы с инжектированными интерфейсами проще: подставляй заглушки вместо реальных сервисов.

13. Unit-тесты: xUnit

xUnit — стандартный тестовый фреймворк .NET:

dotnet new xunit -o MyApp.Tests
public class CalculatorTests {
    [Fact]
    public void Add_ReturnsSum() {
        var calc = new Calculator();
        Assert.Equal(5, calc.Add(2, 3));
    }

    [Theory]
    [InlineData(0, 5)]
    [InlineData(-1, 5)]
    public void Add_Negatives_Work(int a, int b) {
        Assert.Equal(a + b, new Calculator().Add(a, b));
    }
}
Подробнее о практике написания тестов — в курсе Jest (идеи общие), а о генерации тестов нейросетями — в GitHub Copilot.

14. Публикация и деплой: Kestrel + Nginx

ASP.NET Core — кроссплатформенный: приложение собирается в папку и запускается как обычная программа:

dotnet publish -c Release -o /var/www/myapp

В продакшене Kestrel обычно прячут за обратным прокси Nginx: он раздаёт статику, терминирует TLS и проксирует на 127.0.0.1:

location / {
    proxy_pass http://127.0.0.1:5000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Не выставляй Kestrel напрямую в интернет без HTTPS и прокси: так безопаснее и гибче (настройки TLS, кэширование, раздача статики — отдельно).
Упаковка приложения в контейнер и автоматический деплой описаны в курсах Docker и GitHub Actions.

15. Records и неизменяемость данных

record — это неизменяемые классы с «бесплатным» равенством по значениям и синтаксисом with:

public record Order(int Id, decimal Total);

var o1 = new Order(1, 100m);
var o2 = o1 with { Total = 120m };   // копия с изменением

decimal Discount(Order o) => o switch
decimal Price(Order o) => o.Total > 1000 ? o.Total * 0.9m : o.Total;
Records не заменяют классы с побочными эффектами; они хороши для DTO и моделей домена, где важна неизменность.

16. Структурное логирование

Структурные логи — это записи с именованными полями, а не текст. Так их ищут и фильтруют в ELK и Sentry:

Log.Information("Заказ {OrderId} на сумму {Amount}",
    order.Id, order.Total);
// appsettings.json
"Serilog": {
  "WriteTo": [{ "Name": "File", "Args": { "path": "logs/app-.log" } }]
}
Не логируй секреты и персональные данные: маскируй поля перед записью, чтобы логи не стали источником утечек.

17. Профилирование и диагностика

Когда приложение «тормозит», измеряй, а не угадывай. .NET-инструменты анализируют процесс без правок кода:

dotnet-counters monitor -p 1234 --counters System.Runtime
dotnet-trace collect -p 1234 --profile gc-verbose
// из .csproj или команды
dotnet tool install --global dotnet-counters
Профилируй на нагрузке, близкой к боевой: локальный пустой запуск даст ложную картину.

18. Миграция на новые версии .NET

Обновление фреймворка требует плана, а не «пересобрать и посмотреть»:

  1. проверь SupportedOSPlatform и устаревшие API: [Obsolete] подскажет путь перехода;
  2. обнови пакеты NuGet: dotnet list package --outdated;
  3. погоняй тесты и глазом пройди по breaking changes между версиями;
  4. обновляй по одному целевому фреймворку за раз (<TargetFramework>net8.0</TargetFramework>).
dotnet list package --outdated
dotnet nuget update source
Старые библиотеки без поддержки net8.0 переводи на новую версию; .NET Standard нацелен на совместное использование, но живой код лучше обновлять до LTS (.NET 8).

19. Span<T> и работа с памятью

Когда критична скорость (разбор строк, сеть, файлы), избегай лишних аллокаций:

ReadOnlySpan<char> src = "2026:09:19".AsSpan();
var year  = src.Slice(0, 4);
var month = src.Slice(5, 2);   // без подстрок и мусора
stackalloc на стеке быстр, но ограничен по объёму: для больших буферов используй ArrayPool<T>.

20. Доступ к данным: EF Core и Dapper

ORM не обязателен, но без плана по базе будет больно. Выбирай по задаче:

// Dapper — SQL вручную: скорость и контроль
var users = connection.Query<User>(
   "SELECT * FROM users WHERE city = @city", new { city });
// EF: запросы без трекинга — меньше памяти
var items = ctx.Orders.AsNoTracking()
    .Where(o => o.Date > cutoff).ToList();
Избегай N+1: включай Include/Join и профилируй сгенерированный SQL, иначе ORM спрячет медленные запросы.

21. Глобальная обработка ошибок API

Клиент должен получать предсказуемые ошибки, а не голый 500. Строй каркас сразу:

app.UseExceptionHandler(errApp => errApp.Run(async ctx =>
{
    var ex = ctx.Features.Get<IExceptionHandlerFeature>()?.Error;
    ctx.Response.StatusCode = 500;
    await ctx.Response.WriteAsJsonAsync(ProblemDetails(ex));
}));
Соедини с разделом про структурное логирование: ошибка без traceId — сообщение без адресата.

22. Кейс: миграция легаси-приложения

Большие .NET Framework-проекты мигрируют по слоям, а не «переписыванием»:

  1. разнеси сервисы на библиотеки: .NET Framework → netstandard → net8;
  2. напиши контрактные тесты вокруг границ (снимки БД, хрупкие зависимости);
  3. запускай старую и новую версии параллельно и сверяй ответы (split traffic);
  4. убирай старый код, когда сходимость ответов ≥ 99.9%.
// shadow-переключатель: оба пути работают, старый пишет в лог
if (featureFlags.NewEngine) return newImpl(r);
return legacyImpl(r);
Не переноси бизнес-правила по памяти: без контрактных тестов переезд утащит баги с собой, как сувениры.

Что дальше

Ты познакомился с основами C# и .NET: типами, условиями, методами, классами, коллекциями, LINQ, файлами, API и упаковкой. Дальше советуем углубиться в SQL и базы данных — SQL, PostgreSQL, а затем CI/CD в Docker и GitHub Actions. Обмен данными с фронтендом объяснён в REST API.

← Вернуться к курсам