Нововведения в разных версиях языка C#. Текст сгенерирован с помощью DeepSeek.
После чего был исправлен и дополнен ссылками и примерами.
Подробнее можно прочитать у профильных блогеров: блогер 1, блогер 2, блогер 3, блогер 4, блогер 5.
Также есть похожая статья на сайте Микрософт, на сайте Википедии, ответ в Stack Overflow, вехи в github.
Для практики можно воспользоваться бесплатными курсами от Микрософт.
От себя добавлю, что в проекте можно менять версию .NET, но нельзя менять версию языка C#.
Скажем, Visual Studio 2019 (2022) выставляет версию языка в 7.3 для классического .NET Framework любой версии.
Visual Studio 2022 для .NET Core 3, 5, 6, 7, 8, 9 выставляет версию языка 8, 9, 10, 11, 12, 13 соотвественно.
Уточнить версию компилятора можно вставив в исходники строку #error version.
В статье я рассматриваю только изменения синтаксиса языка, нововведения в стандартной библиотеке не упоминаются.
- Основные нововведения:
- Базовые функции языка: классы, структуры, интерфейсы, делегаты, события, атрибуты, исключения, пространства имен
- Поддержка ООП (наследование, полиморфизм, инкапсуляция): документация
- Управляемый код и сборка мусора
- Версия .NET: .NET Framework 1.0.
- Версия Visual Studio: Visual Studio .NET 2002.
Пример кода C# 1.0
using System;
namespace CSharp_1
{
interface IShape
{
string Name { get; }
double CalcArea();
double CalcPerimeter();
string Info();
}
class Rectangle : IShape
{
double _width, _height;
public Rectangle(double width, double height)
{
if (width <= 0 || height <= 0)
throw new ArgumentException("Сторона должна быть больше 0");
_width = width;
_height = height;
}
public string Name { get { return "Прямоугольник"; } }
public double CalcArea()
{
return _width * _height;
}
public double CalcPerimeter()
{
return 2 * (_width + _height);
}
public string Info()
{
return string.Format("ширина: {0:0.##}, высота: {1:0.##}", _width, _height);
}
}
class Circle : IShape
{
double _radius;
public Circle(double radius)
{
_radius = radius;
}
public string Name { get { return "Круг"; } }
public double CalcArea()
{
return Math.PI * _radius * _radius;
}
public double CalcPerimeter()
{
return 2 * Math.PI * _radius;
}
public string Info()
{
return string.Format("радиус: {0:0.##}", _radius);
}
}
delegate void ShapeEventHandler(string message);
class ShapeManager
{
public event ShapeEventHandler ShapeCreated;
public void CreateShape(IShape shape)
{
if (ShapeCreated != null)
{
ShapeCreated(string.Format(
"Создана фигура: {0}, {1}, периметр: {2:0.##}, площадь: {3:0.##}",
shape.Name, shape.Info(), shape.CalcPerimeter(), shape.CalcArea()
));
}
}
}
class Program
{
static void ShowInfo(string message)
{
Console.WriteLine(message);
}
public static void Main()
{
try
{
ShapeManager sm = new ShapeManager();
sm.ShapeCreated += new ShapeEventHandler(ShowInfo);
Rectangle rect = new Rectangle(10, 20);
sm.CreateShape(rect);
Circle circle = new Circle(10.1010);
sm.CreateShape(circle);
Rectangle invalidRect = new Rectangle(-20, -30);
sm.CreateShape(invalidRect);
}
catch (ArgumentException ex)
{
Console.WriteLine("Ошибка: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Неизвестная ошибка: " + ex.ToString());
}
}
}
}- Основные нововведения:
foreachвызываетDispose- Исправления и оптимизация
- Версия .NET: .NET Framework 1.1.
- Версия Visual Studio: Visual Studio .NET 2003.
Пример кода C# 1.2
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
class Range : IEnumerable, IEnumerator, IDisposable
{
int[] _data;
int _pos = -1;
public Range(int start, int end)
{
if (end < start)
throw new Exception("Invalid range");
_data = new int[end - start];
for (int i = 0; i < end - start; i++)
_data[i] = i + start;
}
public object Current { get { return _data[_pos]; } }
public void Dispose()
{
Console.WriteLine("Dispose"); //not called in c# 1.0
}
public IEnumerator GetEnumerator()
{
this.Reset();
return this;
}
public bool MoveNext()
{
_pos++;
return _pos < _data.Length;
}
public void Reset()
{
_pos = -1;
}
}
class Program
{
static void Main(string[] args)
{
IEnumerable r = new Range(1, 3);
foreach (var i in r)
Console.WriteLine(i);
foreach (var i in r)
Console.WriteLine(i);
}
}- Основные нововведения:
- Обобщения (Generics): документация
- Анонимные методы: документация
- Статические классы: документация
- Итераторы (
yield): документация - Nullable типы: документация
- Частичные классы и интерфейсы: документация
- Ковариация и контравариация в делегатах: документация
- Версия .NET: .NET Framework 2.0.
- Версия Visual Studio: Visual Studio 2005.
Пример кода C# 2.0
using System;
using System.Collections.Generic;
namespace CSharp_2
{
delegate void DoAction<T>(T item);
static class CollectionUtils
{
public static List<T> CreateList<T>(params T[] items)
{
return new List<T>(items);
}
public static IEnumerable<T> Reverse<T>(IEnumerable<T> items)
{
List<T> buffer = new List<T>(items);
for (int i = buffer.Count - 1; i >= 0; i--)
yield return buffer[i];
}
public static void ForEach<T>(IEnumerable<T> items, DoAction<T> action, bool reverse)
{
if (reverse)
items = Reverse(items);
foreach (T item in items)
action(item);
}
}
class Program
{
public static void Main()
{
List<int?> list = CollectionUtils.CreateList<int?>(1, 2, null, 4, 5);
CollectionUtils.ForEach(list, delegate (int? i)
{
Console.WriteLine(i * i ?? default(int));
}, true);
}
}
}- Основные нововведения:
- LINQ (Language Integrated Query): документация
- Лямбда-выражения: документация
- Ключевое слово
var: документация - Анонимные типы: документация
- Методы расширения: документация
- Автоматические свойства: документация
- Инициализаторы объектов и коллекций: документация
- Частичные методы: документация
- Версия .NET: .NET Framework 3.5.
- Версия Visual Studio: Visual Studio 2008.
Пример кода C# 3.0
using System;
using System.Collections.Generic;
using System.Linq;
namespace CSharp_3
{
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
static class PersonExtension
{
public static bool IsAdult(this Person p)
{
return p.Age >= 18;
}
}
class Program
{
public static void Main()
{
var list = new List<Person>
{
new Person { Name = "Alex", Age = 10},
new Person { Name = "Ivan", Age = 20},
new Person { Name = "Peter", Age = 30}
};
var filtered = list.Where(p => p.IsAdult()).Select(p => new { p.Name });
foreach (var p in filtered)
Console.WriteLine(p.Name);
}
}
}- Основные нововведения:
- Динамическая типизация (
dynamic): документация - Именованные и необязательные аргументы: документация
- Ковариация и контравариация в обобщенных интерфейсах и делегатах: документация
- Динамическая типизация (
- Версия .NET: .NET Framework 4.0.
- Версия Visual Studio: Visual Studio 2010.
Пример кода C# 4.0
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp13
{
class Program
{
class Bot
{
public void Hello(string prefix = "") { Console.WriteLine(prefix + "Hello"); }
public void Bye(string prefix = "") { Console.WriteLine(prefix + "Bye"); }
}
static void Main(string[] args)
{
var bots = new Bot[5];
for (int i = 0; i < bots.Length; i++)
bots[i] = new Bot();
Parallel.For(0, bots.Length, i => Print(bots[i], num: i, delay: 100));
}
static void Print(dynamic obj, int num, int delay = 10)
{
obj.Hello("Bot " + num + " say: ");
Thread.Sleep(delay);
obj.Bye("Bot " + num + " say: ");
}
}
}- Основные нововведения:
- Асинхронное программирование (
async/await): документация - Информация о вызывающем методе (Caller Info Attributes): документация
- Асинхронное программирование (
- Версия .NET: .NET Framework 4.5.
- Версия Visual Studio: Visual Studio 2012.
Пример кода C# 5.0
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
public enum LogLevel { Debug, Info, Warning, Error }
public static class Logger
{
public static void Log(
string message,
LogLevel level = LogLevel.Info,
[CallerMemberName] string callerName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
string fileName = System.IO.Path.GetFileName(callerFilePath);
string levelStr = level.ToString().ToUpper();
Console.WriteLine($"[{timestamp}] [{levelStr}] [{callerName}() in {fileName}:{callerLineNumber}] {message}");
}
}
public static class ConsoleApplication
{
public static void Main()
{
Logger.Log("Начало программы", LogLevel.Debug);
MethodAsync().GetAwaiter().GetResult();
}
private static async Task MethodAsync()
{
Logger.Log(">MethodAsync");
await Task.Delay(100);
Logger.Log("<MethodAsync");
}
}- Основные нововведения:
- Инициализаторы свойств: документация
- Лямбда-выражения (
=>) для методов и свойств: документация - Интерполяция строк (
$""): документация - Выражение
nameof: документация - Фильтры исключений (
when): документация - Null-условный оператор (
?.): документация - Статические импорты (
using static): документация
- Версия .NET: .NET Framework 4.6, .NET Core 1.0.
- Версия Visual Studio: Visual Studio 2015.
Пример кода C# 6.0
using System;
using static System.Console;
public class Person
{
public string Name { get; } = "Anonymous";
public int Age { get; set; } = 18;
public Person(string name) => Name = name;
public override string ToString() => $"Name: {Name}, Age: {Age}";
}
public class Program
{
public static void Main()
{
try
{
var person = new Person("Alice");
person.Age = -1;
WriteLine(person); // "Name: Alice, Age: -1"
// Проверка nameof (используется для безопасного получения имени переменной)
ValidatePerson(person);
}
catch (Exception ex) when (ex.Message.Contains("Age")) // Фильтр исключений
{
WriteLine($"Проблема с возрастом: {ex.Message}");
}
catch (Exception ex)
{
WriteLine($"Ошибка: {ex.Message}");
}
}
// Метод с использованием nameof для валидации
public static void ValidatePerson(Person person)
{
if (person == null)
throw new ArgumentNullException(nameof(person)); // Безопасное имя параметра
if (person.Age < 0)
throw new ArgumentException("Age cannot be negative", nameof(person.Age));
}
}- Основные нововведения:
- Кортежи и деконструкция: кортежи, деконструкция
- Сопоставление с образцом (
pattern matching): документация - Локальные функции: документация
refвозвращаемые значения и локальные переменные: документация- Упрощенное объявление
outпеременных: документация - Бинарные литералы и целочисленные разделители: документация
- Больше возвращаемых типов в асинхронных методах: документация
- Лямбда-выражения для остальных частей класса: документация
throwвыражения: документация- Отбрасываемые переменные (
_): документация - Частичные структуры: документация
- Версия .NET: .NET Framework 4.7, .NET Core 2.0.
- Версия Visual Studio: Visual Studio 2017.
подробнее с примерами, серия статей
Пример кода C# 7.0
using System;
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
class Program
{
static void PrintPoints(Point[] points)
{
foreach (var p in points)
PrintPoint(p);
void PrintPoint(Point p) => Console.WriteLine(p.X + " " + p.Y);
}
static ref Point GetPoint(Point[] points)
{
return ref points[0];
}
static void Main()
{
int.TryParse("1", out int val);
Point[] points = { new Point(val, 2_0), new Point(3, 4) };
ref var p = ref GetPoint(points);
p = new Point(5, 6);
var (x, y) = p;
PrintPoints(points);
switch (points[0])
{
case Point point when point.X == x:
Console.WriteLine("Test ok");
break;
}
}
}- Основные нововведения:
- Асинхронный
Main: документация - Вывод типов для
default: документация - Вывод имени члена кортежа: документация
- Именованные параметры конструктора атрибута: документация
- Сопоставление с образцом для обобщений: документация
- Асинхронный
- Версия .NET: .NET Framework 4.7.1, .NET Core 2.0.
- Версия Visual Studio: Visual Studio 2017 (15.3+).
Пример кода C# 7.1
using System;
using System.Threading.Tasks;
class Program
{
[Obsolete(message: "Этот метод устарел")]
static async Task Main()
{
int count = 1000;
string msg = default;
var t = (count, msg);
await Task.Delay(t.count);
}
}- Основные нововведения:
- Модификатор
in: документация - Упрощение в именованных аргументах: документация
- Безопасный
stackalloc: документация private protected: документация- Условные
ref-выражения: документация - Модификатор возвращаемого значения
ref readonly: документация - Неизменяемые структуры
readonly struct: документация - Стековые структуры
ref struct: документация
- Модификатор
- Версия .NET: .NET Framework 4.7.2, .NET Core 2.0.
- Версия Visual Studio: Visual Studio 2017 (15.5+).
Пример кода C# 7.2
using System;
readonly struct Point
{
public Point(int x, int y) => (X, Y) = (x, y);
public int X { get; }
public int Y { get; }
public override string ToString() => $"({X}, {Y})";
public double DistanceTo(Point p) => Math.Sqrt(Math.Pow(X - p.X, 2) + Math.Pow(Y - p.Y, 2));
}
class Program
{
static void Main(string[] args)
{
ref readonly Point GetClosest(in Point t, in Point a, in Point b) =>
ref (t.DistanceTo(a) < t.DistanceTo(b) ? ref a : ref b);
Point target = new Point(1, -1);
Point[] points = new Point[] { new Point(2, 3), new Point(3, 2) };
ref readonly var closest = ref GetClosest(t: target, points[0], b: points[1]);
Console.WriteLine($"Точка {target}");
Console.WriteLine($"Массив точек: {String.Join(", ", points)}");
Console.WriteLine($"Ближайшая точка в массиве: {closest}");
}
}- Основные нововведения:
- Сравнение кортежей: документация
- Новые ограничения параметра типа: документация
- Переназначение локальных ссылок
ref: документация - Улучшения
fixed: документация - Улучшения
stackalloc: документация
- Версия .NET: .NET Framework 4.8, .NET Core 2.1.
- Версия Visual Studio: Visual Studio 2017 (15.7+).
подробнее с примерами: часть 1, часть 2
Пример кода C# 7.3
using System;
public class Example
{
public static void Main()
{
int x = 5, y = 10;
ref int r = ref x;
r = ref y;
r = 42;
var tuple = (x, y);
Console.WriteLine(tuple == (5, 42));
}
}- Основные нововведения:
- Nullable ссылочные типы: документация
- Асинхронные потоки (
await foreach): документация - Асинхронно освобождаемые объекты (
await using): документация - Индексы и диапазоны: документация
- Реализация по умолчанию в интерфейсах: документация
- Выражение
switch: документация - Улучшенное сопоставление шаблонов: документация
- Декларации
using: документация - Статические локальные функции: документация
readonlyчлены в структурах: документация- Присваивание при значении
null: документация
- Версия .NET: .NET Core 3.0, .NET Standard 2.1.
- Версия Visual Studio: Visual Studio 2019.
подробнее с примерами: серия статей
Пример кода C# 8.0
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
internal class Program
{
static async Task Main(string[] args)
{
int[]? arr = null;
var list = new List<int>();
await foreach (var i in GetIntAsync())
list.Add(i);
arr ??= list.ToArray()[2..7];
ProcessRange(null, arr);
ProcessRange("null", null);
ProcessRange("empty", Array.Empty<int>());
static async IAsyncEnumerable<int> GetIntAsync()
{
for (int i = 0; i < 10; i++)
yield return i;
}
static void ProcessRange(string? title, int[]? range)
{
var mess = range switch
{
null => "null",
_ when range.Length == 0 => "empty",
_ => string.Join(", ", range)
};
Console.WriteLine(@$"{title ?? "range"}: {mess}");
}
}
}- Основные нововведения:
- Операторы верхнего уровня: документация
- Записи (Records): документация
- Инициализаторы только для чтения (
init): документация - Выражения
withдля записей: документация - Сопоставление с образцом для логических выражений: документация
- Улучшенные частичные методы: документация
- Версия .NET: .NET 5.0.
- Версия Visual Studio: Visual Studio 2019 (16.8+).
подробнее с примерами: серия статей
Пример кода C# 9.0
using System;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
var client = new HttpClient();
var data = await client.GetStringAsync("https://jsonplaceholder.typicode.com/posts");
var posts = JsonSerializer.Deserialize<Post[]>(data)!;
var firstPost = posts.Where(p => p is { id: 1 }).First();
Console.WriteLine(firstPost);
var copyPost = firstPost with { title = "New title" };
copyPost.body = "New body";
Console.WriteLine(copyPost);
record Post (int id, int userId, string title)
{
public string body { get; set; }
public override string ToString() =>
$"id: {id}, userId: {userId}, title: {Trunc(title, 20)}, body: {Trunc(body?.Replace('\n', ' '), 20)}";
string Trunc(string str, int len) =>
str?.Length > len ? str[..len] + "..." : str;
}- Основные нововведения:
- Улучшенные структуры - инициализация свойств и полей, конструктор без параметров,
with: конструкторы и инициализация, with - Структуры записей (
record struct): документация - Глобальные
using-директивы: документация - Неявные
using-директивы: документация - Упрощенное объявление пространств имен: документация
- Расширенные шаблоны свойств: документация
- Естественный тип ламбда-выражения: документация
- Явный возвращаемый тип лямбда-выражения: документация
- Константные интерполируемые строки: документация
- Улучшенные структуры - инициализация свойств и полей, конструктор без параметров,
- Версия .NET: .NET 6.0.
- Версия Visual Studio: Visual Studio 2022.
Пример кода C# 10.0
global using System;
namespace CSharp10Sample;
readonly record struct Number(int value)
{
public override string ToString() => value.ToString();
}
class Program
{
static void Main()
{
var n = new Number();
n = n with { value = 10 };
var square = Number (Number n) => new Number(n.value * n.value);
const string title = $"Just for information";
Console.WriteLine($"{title}: {n} * {n} = {square(n)}");
}
}- Основные нововведения:
- Необработанные строковые литералы (
"""): документация - Модификатор
fileдля типов: документация - Обязательные члены (
required): документация - Универсальные атрибуты: документация
- Шаблоны списков: документация
- Строковые литералы
UTF-8: документация - Статические абстрактные (
static abstract) и статические виртуальные (static virtual) члены интерфейса: документация
- Необработанные строковые литералы (
- Версия .NET: .NET 7.0.
- Версия Visual Studio: Visual Studio 2022 (17.4+).
подробнее с примерами, на сайте микрософт
Пример кода C# 11.0
var container = ContainerString.Create("""Apple, Cherry""");
var items = container.List switch
{
["Apple, Plum"] => "Apple and Plum",
[.., "Cherry"] => "Last Cherry",
_ => "Unknown container"
};
Console.WriteLine(items);
file interface IContainer<T> where T: IContainer<T>
{
static abstract T Create(string data);
}
file class ContainerString : IContainer<ContainerString>
{
public required List<string> List { get; init; }
public static ContainerString Create(string data) => new()
{
List = data
.Split(",")
.Select(s => s.Trim())
.ToList()
};
}- Основные нововведения:
- Первичные конструкторы: документация
- Выражения коллекции: документация
- Параметры лямбда-кода по умолчанию: документация
- Модификатор
ref readonlyдля параметров метода: документация - Псевдоним любого типа: документация
- Версия .NET: .NET 8.0.
- Версия Visual Studio: Visual Studio 2022 (17.8+).
подробнее с примерами, на сайте микрософт
Пример кода C# 12.0
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using IntContainerInfo = (int Count, int Sum);
IntContainer data = [1, 2, 3];
Span<int> add = stackalloc int[] { 4, 1 };
add[0] = 5;
data = data.Concat(ref add).Exclude(1);
Console.WriteLine($"Content: {data}, Info: {data.Info}");
[CollectionBuilder(typeof(IntContainer), "Create")]
public class IntContainer(ReadOnlySpan<int> items): IEnumerable<int>
{
private readonly int[] _items = items.ToArray();
public static IntContainer Create(ReadOnlySpan<int> items) => new(items);
public IntContainer Concat(ref readonly Span<int> items) => new([.. _items, .. items]);
public IntContainer Exclude(int v = 0) => new(_items.Where(i => i != v).ToArray());
public IntContainerInfo Info => (_items.Count(), _items.Sum());
public override string ToString() => $"[{string.Join(", ", _items)}]";
public IEnumerator<int> GetEnumerator() => _items.AsEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}- Основные нововведения:
- Коллекции
params: документация \eescape-последовательность: документация- Оператор
^в инициализаторе объекта с индексом: документация - Частичные свойства и индексаторы: документация
refиunsafeв итераторах иasyncметодах: документация- Реализация интерфейсов в
ref struct: документация - Приоритет разрешения перегрузки: документация
- Анти-ограничение параметра типа
allows ref struct: документация
- Коллекции
- Версия .NET: .NET 9.0.
- Версия Visual Studio: Visual Studio 2022 (17.12+).
подробнее с примерами, на сайте микрософт
Пример кода C# 13.0
using System;
using System.Runtime.CompilerServices;
class Program
{
partial class ExtendableArray<T>
{
int _count;
T[] _data;
public ExtendableArray(int capacity = 0) => _data = new T[_count = capacity];
[OverloadResolutionPriority(1)]
public partial T this[Index i] { get; set; }
public T this[int i] => _data[i];
public override string ToString() => $"[{string.Join(", ", new ArraySegment<T>(_data, 0, _count))}]";
}
partial class ExtendableArray<T>
{
public partial T this[Index i]
{
get
{
CheckSize(i.GetOffset(_data.Length) + 1);
return _data[i];
}
set
{
CheckSize(i.GetOffset(_data.Length) + 1);
_data[i] = value;
}
}
private void CheckSize(int size)
{
_count = Math.Max(_count, size);
bool skip = _data.Length >= size;
string start = skip ? "\e[32m" : "\e[31m";
string end = "\e[0m";
Console.WriteLine($"CheckSize {size}, skip={start}{skip}{end}");
if (skip)
return;
int capacity = _data.Length == 0 ? 1 : _data.Length;
while (capacity < size)
capacity *= 2;
var data = _data;
_data = new T[capacity];
data.CopyTo(_data, 0);
}
}
public static void Main()
{
var list = new ExtendableArray<int>(3)
{
[0] = 1,
[^1] = 2
};
list[3] = 3;
list[4] = 1 + list[3];
Console.WriteLine(list);
}
}- Основные нововведения:
- Члены расширения: документация
- Условное присваивание
null: документация nameofподдерживает несвязанные универсальные типы: документация- Более неявные преобразования для
Span<T>иReadOnlySpan<T>: документация - Модификаторы простых лямбда-параметров: документация
fieldподдерживаемые свойства: документацияpartialсобытия и конструкторы: документация
- Версия .NET: .NET 10.0.
- Версия Visual Studio: Visual Studio 2022.