C# / .NET — платформа от Microsoft
C# — современный объектно-ориентированный язык от Microsoft, а .NET — платформа, на которой он работает: на Windows, macOS и Linux. На C# пишут веб-сервисы на ASP.NET Core, десктопные приложения, игры в Unity и инструменты для разработки. Стабильный выбор для карьеры в enterprise.
1. Что такое .NET и C#
.NET — это среда исполнения и библиотека классов. Программа на C# компилируется в промежуточный код (IL), который выполняет виртуальная машина .NET с сборщиком мусора. Современная версия — .NET 8+, единая для всех платформ.
- C# — язык программирования (синтаксис, типы, ООП).
- .NET Runtime — исполняет IL-код, управляет памятью, обеспечивает безопасность типов.
- ASP.NET Core — веб-фреймворк для API и сайтов.
- EF Core — ORM для работы с базами данных.
- xUnit / NUnit — популярные фреймворки тестирования.
| Область | Инструмент |
|---|---|
| Веб-API | ASP.NET Core Web API |
| Веб-сайты | ASP.NET Core MVC / Razor Pages, Blazor |
| Десктоп | WPF, WinForms, Avalonia |
| Игры | Unity (использует C#) |
| Мобильные | .NET MAUI |
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># полезные команды
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; // ошибка компиляцииОсновные типы
| Тип | Размер | Пример |
|---|---|---|
| int | 4 байта | 42 |
| long | 8 байт | 1000000000L |
| double | 8 байт | 3.14 |
| decimal | 16 байт | 199.95m |
| bool | 1 бит | true |
| char | 2 байта | 'Ж' |
| 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 = false4. Условия и циклы
Конструкции 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
}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;
}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 | в пределах сборки |
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 | группировка |
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
{
var text = File.ReadAllText("config.json");
Console.WriteLine(text);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Конфиг не найден: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Ошибка: " + ex.Message);
}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-код |
|---|---|---|
| Ok | Results.Ok(value) | 200 |
| Created | Results.Created(url, value) | 201 |
| Not Found | Results.NotFound() | 404 |
| Bad Request | Results.BadRequest() | 400 |
| No Content | Results.NoContent() | 204 |
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 | запустить локально |
11. Асинхронность: async/await
Асинхронный код не блокирует поток выполнения, пока ждёт сеть или диск. Ключевые правила async/await:
- метод, который «ждёт», помечается
asyncи возвращаетTask; - внутри используй
awaitвместо блокирующих .Result / .Wait(); - в UI-приложениях await сам возвращается в поток интерфейса;
- async void — только для обработчиков событий.
public async Task<string> LoadAsync() {
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com/data");
}Task.WhenAll — это сокращает общее время ожидания без распараллеливания потоков.12. Dependency Injection
DI-контейнер веб-приложений собирает зависимости объектов автоматически. Типичная регистрация в Program.cs:
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddTransient<IEmailer, SmtpEmailer>();
builder.Services.AddSingleton<ICache, RedisCache>();- Singleton — один объект на всё приложение;
- Scoped — один на HTTP-запрос (стандарт для репозиториев);
- Transient — новый на каждое обращение.
public class OrderService {
public OrderService(IOrderRepository repo, ILogger<OrderService> log) {
_repo = repo; _log = log; // зависимости приходят «извне»
}
}13. Unit-тесты: xUnit
xUnit — стандартный тестовый фреймворк .NET:
dotnet new xunit -o MyApp.Testspublic 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));
}
}- [Fact] — одиночный тест;
- [Theory] + [InlineData] — тест-множество с разными данными;
- именование: Метод_Условие_Ожидание;
- тестируй поведение, а не внутренности.
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;
}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 switchrecordсравнивается по полям, не по ссылке — удобно в тестах;withне мутирует исходный объект, а создаёт копию;- позиционный синтаксис даёт деконструкцию и pattern matching:
o is Order(_, > 1000).
decimal Price(Order o) => o.Total > 1000 ? o.Total * 0.9m : o.Total;16. Структурное логирование
Структурные логи — это записи с именованными полями, а не текст. Так их ищут и фильтруют в ELK и Sentry:
Log.Information("Заказ {OrderId} на сумму {Amount}",
order.Id, order.Total);- используй шаблоны сообщений, а не конкатенацию строк;
- уровни: Debug — отладка, Information — бизнес-события, Error — исключения с контекстом;
- добавляй корреляционный id запроса, чтобы собрать цепочку действий.
// 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- dotnet-counters: CPU, память, исключения в реальном времени;
- dotnet-trace: стек вызовов и GC-профиль — найдёшь, что аллоцирует;
- Visual Studio и Rider показывают то же в IDE через Attach to process.
// из .csproj или команды
dotnet tool install --global dotnet-counters18. Миграция на новые версии .NET
Обновление фреймворка требует плана, а не «пересобрать и посмотреть»:
- проверь SupportedOSPlatform и устаревшие API:
[Obsolete]подскажет путь перехода; - обнови пакеты NuGet:
dotnet list package --outdated; - погоняй тесты и глазом пройди по breaking changes между версиями;
- обновляй по одному целевому фреймворку за раз (
<TargetFramework>net8.0</TargetFramework>).
dotnet list package --outdated
dotnet nuget update source19. Span<T> и работа с памятью
Когда критична скорость (разбор строк, сеть, файлы), избегай лишних аллокаций:
ReadOnlySpan<char> src = "2026:09:19".AsSpan();
var year = src.Slice(0, 4);
var month = src.Slice(5, 2); // без подстрок и мусораSpan<T>— безопасная работа с памятью без GC-нагрузки;Memory<T>— асинхронные пути и хранение в структурах;- бенчмарк обязателен: выигрыш не всегда так велик, как кажется.
stackalloc на стеке быстр, но ограничен по объёму: для больших буферов используй ArrayPool<T>.20. Доступ к данным: EF Core и Dapper
ORM не обязателен, но без плана по базе будет больно. Выбирай по задаче:
// Dapper — SQL вручную: скорость и контроль
var users = connection.Query<User>(
"SELECT * FROM users WHERE city = @city", new { city });- EF Core — скорость разработки: миграции, LINQ, трекинг;
- Dapper — сырой SQL для горячих путей и сложных JOIN;
- гибрид: EF для CRUD-админки + Dapper для отчётов.
// EF: запросы без трекинга — меньше памяти
var items = ctx.Orders.AsNoTracking()
.Where(o => o.Date > cutoff).ToList();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));
}));- валидационные 400 с массивом полей — единый формат (
ProblemDetails); - не отдавай детали исключений наружу: стектрейсы — добыча для атакующего;
- логируй traceId: трейс свяжет ошибку с конкретным запросом.
22. Кейс: миграция легаси-приложения
Большие .NET Framework-проекты мигрируют по слоям, а не «переписыванием»:
- разнеси сервисы на библиотеки: .NET Framework → netstandard → net8;
- напиши контрактные тесты вокруг границ (снимки БД, хрупкие зависимости);
- запускай старую и новую версии параллельно и сверяй ответы (split traffic);
- убирай старый код, когда сходимость ответов ≥ 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.