В C# 12 упрощен способ создания массивов и коллекций с помощью выражения коллекций (collection expression), которые представляют унифицированный подход к созданию коллекций. Так, если раньше создание массивов выглядело так:
int[] nums1 = { 1, 2, 3, 4 }; int[] nums2 = new int[] { }; // пустой массив
Теперь можно писать так:
int[] nums1 = [ 1, 2, 3, 4 ]; int[] nums2 = []; // пустой массив
Аналогичным образом можно использовать выражения коллекций для создания других типов коллекций:
List<int> list1 = [1, 2, 3, 4]; List<int> list2 = []; // пустой список Span<int> span1 = [1, 2, 3, 4];
Первичные конструкторы (Primary constructors) позволяют добавлять параметры к определению класса/структуры и использовать эти параметры внутри класса/структуры:
var tom = new Person("Tom", 38); Console.WriteLine(tom); public class Person(string name, int age) { public Person(string name) : this(name, 18) { } public string Name => name; public int Age => age; public override string ToString() => $"name: {name}, age: {age}"; }
Здесь для класса Person определен первичный конструктор с двумя параметрами - name и age. Эти параметры применяются для инициализации свойств Name и Age и используются в методе ToString().
За кадром для каждого параметра первичного конструктора в классе создается приватное поле, которое хранит значение параметра. Благодаря этому они могут использоваться в теле класса.
Кроме первичных конструкторов класс может определять дополнительные конструкторы, как примере выше. Но эти дополнительные конструкторы должны вызывать первичный конструктор:
public Person(string name) : this(name, 18) { }
C# 12 позволяет определять псевдонимы для любых типов. Например:
using People = System.Collections.Generic.List<Person>; People people = new(){ new ("Tom", 38), new ("Bob", 42) }; people.ForEach(Console.WriteLine); public record Person(string Name, int Age);
Здесь "People" выступает в качестве псевдонима для типа List<Person>
Другой пример:
using user = (string, int); user tom = ("Tom", 38); Console.WriteLine(tom); // (Tom, 38)
Здесь имя user выступает в качестве псевдонима для кортежа типа (string, int)
Причем мы можем дать элементам кортежа имя:
using user = (string name, int age); user tom = ("Tom", 38); Console.WriteLine(tom.name); // Tom Console.WriteLine(tom.age); // 38
Еще один пример:
using BinaryOp = System.Func<int, int, int>; BinaryOp sum = (a, b) => a + b; BinaryOp subtract = (a, b) => a - b; DoOperation(10, 6, sum); // 16 DoOperation(10, 6, subtract); // 4 void DoOperation(int a, int b, BinaryOp op) { Console.WriteLine(op(a, b)); }
Здесь для делегата типа System.Func<int, int, int>
присваивается псевдоним BinaryOp
.
Начиная с C# 12 параметры лямбда-выражений могут иметь значения по умолчанию:
var welcome = (string message = "hello")=> Console.WriteLine(message); welcome("hello world"); // hello world welcome(); // hello
Чтобы гарантировать, что ref-параметр не изменит своего значения, начиная с версии C# 12 можно применять ref-параметры только для чтения. Такие параметры предваряются ключевым словом readonly:
void Increment(ref readonly int n) { // n++; // нельзя, иначе будет очишка компиляции Console.WriteLine($"Число в методе Increment: {n}"); } int number = 5; Increment(ref number); Console.WriteLine($"Число после метода Increment: {number}");
В данном случае в метод Increment параметр n передается по ссылке и при этом он доступен только для чтения. При попытке изменить его значение мы получим ошибку на этапе компиляции.
Начиная с версии C# 12, если тип - класс/структура/интерфейс имеют пустое определение (не содержат полей, свойств, методов), то фигурные скобки после названия типа можно не использовать:
class Person; struct User; interface Human;