Noutăți în C# 12
Expresii de colecții
În C# 12, a fost simplificat modul de creare a array-urilor și colecțiilor prin intermediul expresiilor de colecții (collection expression), care oferă o abordare unificată pentru crearea colecțiilor. Astfel, dacă anterior crearea array-urilor arăta astfel:
int[] nums1 = { 1, 2, 3, 4 };
int[] nums2 = new int[] { }; // array gol
Acum poți scrie astfel:
int[] nums1 = [ 1, 2, 3, 4 ];
int[] nums2 = []; // array gol
În mod similar, poți folosi expresii de colecții pentru a crea alte tipuri de colecții:
List<int> list1 = [1, 2, 3, 4];
List<int> list2 = []; // listă goală
Span<int> span1 = [1, 2, 3, 4];
Constructori primari
Constructorii primari (Primary constructors) permit adăugarea de parametri la definirea clasei/structurii și utilizarea acestor parametri în interiorul clasei/structurii:
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}";
}
Aici, pentru clasa Person este definit un constructor primar cu doi parametri - name și age. Acești parametri sunt folosiți pentru a inițializa proprietățile Name și Age și sunt utilizați în metoda ToString().
În fundal, pentru fiecare parametru al constructorului primar, în clasă se creează un câmp privat care stochează valoarea parametrului. Datorită acestui fapt, acești parametri pot fi utilizați în corpul clasei.
Pe lângă constructorii primari, clasa poate defini constructori suplimentari, așa cum este exemplificat mai sus. Însă acești constructori suplimentari trebuie să apeleze constructorul primar:
public Person(string name) : this(name, 18) { }
Pseudonime pentru tipuri
C# 12 permite definirea de pseudonime pentru orice tipuri. De exemplu:
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);
Aici, "People" servește ca pseudonim pentru tipul List<Person>.
Un alt exemplu:
using user = (string, int);
user tom = ("Tom", 38);
Console.WriteLine(tom); // (Tom, 38)
Aici, numele user servește ca pseudonim pentru un tuplu de tip (string, int).
Mai mult, putem da nume elementelor tuplului:
using user = (string name, int age);
user tom = ("Tom", 38);
Console.WriteLine(tom.name); // Tom
Console.WriteLine(tom.age); // 38
Încă un exemplu:
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));
}
Aici, delegatul de tip System.Func<int, int, int> primește pseudonimul BinaryOp.
Valori implicite pentru parametrii expresiilor lambda
Începând cu C# 12, parametrii expresiilor lambda pot avea valori implicite:
var welcome = (string message = "hello") => Console.WriteLine(message);
welcome("hello world"); // hello world
welcome(); // hello
Parametri ref doar pentru citire
Pentru a garanta că un parametru ref nu își va schimba valoarea, începând cu versiunea C# 12, se pot aplica parametri ref doar pentru citire. Acești parametri sunt precedați de cuvântul cheie readonly:
void Increment(ref readonly int n)
{
// n++; // nu este permis, altfel va apărea o eroare de compilare
Console.WriteLine($"Numărul în metoda Increment: {n}");
}
int number = 5;
Increment(ref number);
Console.WriteLine($"Numărul după metoda Increment: {number}");
În acest caz, în metoda Increment, parametrul n este transmis prin referință și este disponibil doar pentru citire. Dacă se încearcă modificarea valorii sale, vom primi o eroare la compilare.
Simplificarea definirii tipurilor
Începând cu versiunea C# 12, dacă un tip - clasă/structură/interfață - are o definiție goală (nu conține câmpuri, proprietăți, metode), acoladele după numele tipului pot fi omise:
class Person;
struct User;
interface Human;