C# в 2025 году: что нового на практике?
Мир .NET не стоит на месте. В 2025 году язык C# продолжает активно развиваться, предлагая разработчикам инструменты для написания более лаконичного, безопасного и производительного кода. Каждое обновление платформы делает повседневную разработку удобнее, а новые языковые конструкции позволяют избавиться от шаблонного кода.
В этой статье мы разберём три ключевых нововведения C# в 2025 году, которые вы можете начать использовать прямо сейчас. Все примеры кода взяты из реальной практики и адаптированы для образовательного портала pythonlib.ru.
1. Первичные конструкторы (Primary Constructors)
Одно из самых ожидаемых нововведений — это первичные конструкторы для классов и структур. Ранее такая возможность была доступна только для записей (record). Теперь вы можете объявить параметры конструктора прямо в заголовке класса, что значительно сокращает объём кода.
Рассмотрим классический пример создания модели пользователя:
Было (C# 12 и ранее):
public class User
{
public string Name { get; }
public int Age { get; }
public User(string name, int age)
{
Name = name;
Age = age;
}
}
Стало (C# 2025):
public class User(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
public string Greeting => $"Привет, {Name}! Тебе {Age} лет.";
}
Как видите, код стал чище. Параметры name и age теперь доступны во всём теле класса. Это особенно удобно при работе с Dependency Injection (DI) в ASP.NET Core:
public class WeatherService(HttpClient httpClient, ILogger<WeatherService> logger)
{
public async Task<string> GetWeatherAsync(string city)
{
logger.LogInformation("Запрос погоды для города {City}", city);
var response = await httpClient.GetStringAsync($"https://api.weather.com/{city}");
return response;
}
}
Первичные конструкторы позволяют отказаться от множества полей и явных присваиваний, делая код более декларативным.
2. Коллекции нового образца (Collection Expressions)
В 2025 году C# получил мощное обновление для работы с коллекциями. Теперь вы можете инициализировать списки, массивы и даже Span<T> единым синтаксисом — collection expressions.
Новый синтаксис использует квадратные скобки [] вместо фигурных {} или new List<T>():
Пример инициализации различных коллекций:
// Массив
int[] numbers = [1, 2, 3, 4, 5];
// Список
List<string> names = ["Анна", "Борис", "Виктор"];
// Span (работает без аллокаций)
Span<char> letters = ['A', 'B', 'C'];
Главная фишка — оператор распаковки (spread) ... Он позволяет объединять коллекции без вызова .Concat() или .AddRange():
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
// Объединение массивов
int[] combined = [..first, ..second]; // [1, 2, 3, 4, 5, 6]
// Добавление элементов в начало и конец
int[] extended = [0, ..combined, 7]; // [0, 1, 2, 3, 4, 5, 6, 7]
Этот синтаксис работает не только с массивами, но и с List<T>, HashSet<T> и даже с пользовательскими коллекциями, реализующими ICollection<T>. Для разработчиков, привыкших к Python, такой подход покажется очень знакомым и интуитивным.
3. Улучшенная интерполяция строк и обработка исключений
Интерполяция строк в C# 2025 получила несколько важных улучшений, которые делают её ещё более гибкой.
3.1. Интерполяция в операторах nameof
Теперь вы можете использовать nameof с интерполированными строками. Это упрощает логирование и работу с отражением:
string propertyName = "Age";
// Раньше: nameof(User.Age) — только константа
// Теперь можно динамически:
string name = nameof($"{propertyName}"); // "Age"
3.2. Улучшенная обработка исключений
В C# 2025 появилась возможность использовать throw в выражениях с ?? (null-coalescing) и тернарных операторах:
public string GetConfig(string key)
{
return configDictionary.GetValueOrDefault(key)
?? throw new KeyNotFoundException($"Ключ '{key}' не найден.");
}
Это позволяет писать более плоский код без лишних if-блоков.
3.3. Пользовательские обработчики исключений
Новое ключевое слово catch when получило расширенную поддержку для фильтрации исключений по произвольным условиям:
try
{
await ProcessDataAsync();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
LogWarning("Ресурс не найден");
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.InternalServerError)
{
LogError("Ошибка сервера");
throw; // перебрасываем, если не можем обработать
}
Это делает обработку ошибок более точечной и читаемой.
Практический пример: приложение для заметок
Давайте соберём все три нововведения в одном небольшом приложении — консольном менеджере заметок:
// Используем первичный конструктор
public class Note(string title, string content)
{
public string Title { get; } = title;
public string Content { get; } = content;
public DateTime CreatedAt { get; } = DateTime.UtcNow;
}
public class NoteManager
{
private List<Note> _notes = [];
public void AddNote(string title, string content)
{
_notes.Add(new Note(title, content));
}
public void ShowNotes()
{
// Используем collection expressions и интерполяцию
var summaries = [.._notes.Select(n => $"📌 {n.Title} — {n