Версии C#: что скрывается за цифрами и как это работает под капотом
Если вы когда-нибудь заглядывали в обсуждения на Stack Overflow, то наверняка видели вопрос, который собрал тысячи просмотров: "What are the correct version numbers for C#?". Казалось бы, что тут сложного? Открыл документацию — и готово. Но на деле всё оказывается запутаннее. Особенно когда выясняется, что C# 7.0 выходит с .NET Framework 4.7, а C# 8.0 — уже с .NET Core 3.0. И почему C# 5.0 вообще не привязан к какой-то конкретной версии .NET? Добро пожаловать в мир, где цифры обманчивы.
В этой статье мы не просто перечислим версии. Мы заглянем под капот компилятора Roslyn, разберёмся, как номера версий связаны с платформами, и покажем на реальных примерах кода, какие фичи появлялись в каждой версии. Поехали!
Почему вопрос о версиях C# так популярен?
Ответ прост: путаница между версиями языка C# и версиями .NET. Многие разработчики думают, что C# 7.0 — это то же самое, что .NET Framework 4.7. Но это не так. Язык C# живёт своей жизнью, а платформа .NET — своей. Компилятор Roslyn (он же csc.exe) — это мост между ними. Он берёт код на C# и превращает его в IL-код (Intermediate Language), который потом исполняется CLR (Common Language Runtime).
Вот ключевой момент: одна и та же версия компилятора может поддерживать разные версии языка. Например, компилятор из Visual Studio 2019 может компилировать код C# 7.0, 7.1, 7.2, 7.3, 8.0 — в зависимости от настроек проекта. Именно поэтому на Stack Overflow так много путаницы.
Краткая история версий C#
Давайте пробежимся по основным вехам. Я не буду перечислять все 12 версий подряд — лучше выделю те, которые действительно изменили игру.
C# 1.0 (2002) — Начало
Первая версия языка вышла вместе с .NET Framework 1.0. Это был managed-язык с синтаксисом, похожим на Java, но с уникальными фичами вроде свойств (properties) и индексаторов. Никаких LINQ, async/await или generics. Просто, надёжно, скучно.
C# 2.0 (2005) — Generics меняют всё
С этой версией пришли обобщения (generics), анонимные методы, итераторы (yield return) и частичные классы. Это был прорыв: вместо того чтобы писать ArrayList с boxing/unboxing, вы могли использовать List<T>. Производительность взлетела.
// C# 2.0 — generics
List<int> numbers = new List<int> { 1, 2, 3 };
numbers.ForEach(n => Console.WriteLine(n * 2)); // Анонимный метод
C# 3.0 (2007) — LINQ и лямбды
Самая революционная версия. LINQ (Language Integrated Query) изменил подход к работе с данными. Лямбда-выражения, методы расширения, анонимные типы, инициализаторы объектов — всё это появилось здесь. Компилятор Roslyn тогда ещё не родился, но именно в C# 3.0 заложили основы для будущего.
// C# 3.0 — LINQ и лямбды
var evenNumbers = Enumerable.Range(1, 10)
.Where(x => x % 2 == 0)
.Select(x => x * x);
Console.WriteLine(string.Join(", ", evenNumbers)); // 4, 16, 36, 64, 100
C# 5.0 (2012) — Async/await
Асинхронное программирование стало простым как никогда. async и await — это не просто синтаксический сахар. Компилятор генерирует сложный state machine, который управляет потоками. Если вы пишете на C# 5.0 и выше, вы можете забыть о ручном управлении BeginInvoke и EndInvoke.
// C# 5.0 — async/await
async Task<string> FetchDataAsync(string url)
{
using (var client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}
C# 6.0 (2015) — Сахар для повседневности
Эта версия не принесла новых парадигм, но сделала код чище: string interpolation, null-conditional operator (?.), expression-bodied members, using static. Теперь вместо String.Format("Hello {0}", name) вы пишете $"Hello {name}".
// C# 6.0 — string interpolation и null-conditional
string name = "Alice";
Console.WriteLine($"Hello, {name?.ToUpper()}!"); // Hello, ALICE!
C# 7.0–7.3 (2017–2018) — Эволюция
Версия 7.0 принесла кортежи (tuples), pattern matching, локальные функции. 7.1 — async Main, default literals. 7.2 — in-параметры, readonly struct. 7.3 — улучшения для unsafe-кода. Это была эпоха, когда язык становился всё более выразительным.
// C# 7.0 — кортежи и pattern matching
(int sum, int count) = (0, 0);
var result = (sum: 10, count: 5);
Console.WriteLine($"Sum: {result.sum}, Count: {result.count}");
// Pattern matching
object obj = 42;
if (obj is int i && i > 0)
{
Console.WriteLine($"Positive integer: {i}");
}
C# 8.0 (2019) — Nullable reference types
Огромная фича: nullable reference types. Теперь вы можете явно указать, может ли ссылочный тип быть null. Компилятор предупреждает о потенциальных null-опасностях. Это не runtime-защита, но на этапе компиляции — золото.
#nullable enable
string nonNull = "Hello";
string? nullable = null; // Компилятор предупредит, если разыменовать
Console.WriteLine(nonNull.Length); // OK
Console.WriteLine(nullable?.Length); // Безопасно
C# 9.0 (2020) — Records и init-only
Появились records — неизменяемые типы с value-based equality. init-сеттеры для свойств. Top-level statements упростили написание простых программ. Это шаг в сторону функционального программирования.
// C# 9.0 — record
public record Person(string FirstName, string LastName);
var person1 = new Person("John