Выражения коллекций
В 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-параметры только для чтения
Чтобы гарантировать, что 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;