Специальное поле field
Специальное поле field, которое генерируется компилятором, позволяет обращаться внутри свойства к полю, которое хранит значение для данного свойства. Это избавляет от необходимости явного создания поля для свойства. Например, если в предыдущих версиях C# для обращения к полю нам его надо было явно определять:
class Person
{
string? name;
public string? Name
{
get
{
return name; // возвращаем значение свойства
}
set
{
name = value; // устанавливаем новое значение свойства
}
}
}
То начиная с версии C# 14 мы можем вместо него использовать field:
class Person
{
// string? name; это поле больше не нужно
public string? Name
{
get
{
return field; // возвращаем значение свойства
}
set
{
field = value; // устанавливаем новое значение свойства
}
}
}
Существует потенциальная возможность критических изменений или путаницы в типах, которые также содержат компонент (поле/переменную) с именем field, особенно при миграции проектов на C# 14 с более ранних версий.
В этом случае можно использовать @field или this.field для устранения неоднозначности между ключевым словом field и идентификатором, или
всегда можно переименовать компонент field для более четкого различия.
Определение составных операторов присваивания
Начиная с версии C# 14 можно для своих типов настраивать поведение составных операторов присваивания таким образом, чтобы цель присваивания изменялась на месте. При этом язык C# уже в более ранних версиях позволял определять обычный оператор (без присваивания), который также можно использовать в качестве составного оператора с присваиванием. Например:
Counter counter1 = new Counter { Value = 23 };
counter1 += 45;
Console.WriteLine($"counter1.Value: {counter1.Value}"); // 68
class Counter
{
public int Value { get; set; }
public static Counter operator +(Counter c, int n)
{
return new Counter { Value = c.Value + n };
}
}
Таким образом, хотя мы явным образом НЕ определяли составной оператор присваивания += для типа Counter, но благодаря оператору сложения + мы можем выполнять сложение с присвоением.
Однако, инструкция
counter1 += 45;Фактически аналогична следующей:
counter1 = counter1 + 45;То есть при сложении будет создаваться новый объект Counter. И чтобы решить подобную проблему в версию языка C# 14 (.NET 10) была добавлена возможность отдельно определеить составные операции присваивания, которы вместо создания нового объекта
позволяют изменить текущий объект. Фактически, составной оператор присваивания - это метод объекта, который возвращает void (то есть фактически ничего не возвращает), который принимает один параметр и который
имеет специальное имя название оператора:
Counter counter1 = new Counter { Value = 23 };
counter1 += 45;
Console.WriteLine($"counter1.Value: {counter1.Value}"); // 68
class Counter
{
public int Value { get; set; }
public void operator +=(int n)
{
Value += n;
}
}
Подробнее про определение составных операторов присваивания в соответствующей статье.
Блоки расширений
Начиная с версии C# 14 можно использовать новый синтаксис для определения членов расширения - с помощью блоков extension, которые далее будет именовать блоками расширений
Блоки расширения позволяют добавить новые свойства, методы, операторы к уже существующим типам. С помощью блоков расширения можно определять два типа расширений:
Расширения экземпляров: расширяют экземпляр типа
Статические расширения: расширяют сам тип
Пример добавления свойств, методов и операторов к встроенному типу String:
string s = "Привет мир";
Console.WriteLine(s.CharCount('и')); // 2
Console.WriteLine("123".IsNumber); // true
Console.WriteLine("abc".IsNumber); // false
Console.WriteLine("hello" > "ello"); // true
Console.WriteLine("hello" < "ello"); // false
Console.WriteLine(string.Default); // Hello World
static class StringExtension
{
// нестатический блок расширений
extension(string str)
{
public bool IsNumber
{
get
{
for (int i = 0; i < str.Length; i++)
{
if (str[i] < '0' || str[i] > '9') return false;
}
return true;
}
}
public int CharCount(char c)
{
int counter = 0;
for (int i = 0; i < str.Length; i++)
{
if (str[i] == c) counter++;
}
return counter;
}
}
// статический блок расширений
extension(string)
{
// операторы
public static bool operator >(string str1, string str2) => str1.Length > str2.Length;
public static bool operator <(string str1, string str2) => str1.Length < str2.Length;
// статическое свойство
public static string Default => "Hello World";
}
}
Подробнее про блоки расширений в соответствующей статье.
Параметры лямбда-выражения с модификаторами
До C# 14 к параметрам лямбда-выражения можно было добавлять модификаторы (такие как scoped, ref, in, out или ref readonly)
только при условии, что объявления параметров включали типы параметров. Начиная с C# 14 модификаторы можно добавлять без указания типа параметра. Например, раньше мы писали:
var changeAge = (ref int age, int new_value) => {if(new_value > 0) age = new_value;};
То начиная с C# 14 мы можем для параметров не указывать тип:
delegate void ChangeInt(ref int old_value, int new_value);
ChangeInt changeAge = (ref age, new_value) => {if(new_value > 0) age = new_value;};
Единственный минус - в этом случае может потребоваться объявление делегата под такое лямбда-выражение
Пример применения:
ChangeInt changeAge = (ref age, new_value) => {if(new_value > 0) age = new_value;};
int userAge = 34;
changeAge(ref userAge, 41);
Console.WriteLine(userAge); // 41
delegate void ChangeInt(ref int old_value, int new_value);
Присваивание с проверкой на null
Операторы доступа с проверкой на null - ?. и ?[] теперь можно использовать в левой части присваивания или составного присваивания.
До C# 14 перед присваиванием свойству требовалось проверять переменную на null:
Person? person = null;
if(person is not null)
{
person.Name = "Tom";
}
Console.WriteLine(person?.Name); // ничего не выводит
class Person
{
public string? Name {get; set;}
}
Начиная с C# 14 предыдущий код можно упростить с помощью оператора ?.:
Person? person = null;
// присваивание, если только person не равно null
person?.Name = "Tom";
Console.WriteLine(person?.Name); // ничего не выводит
class Person
{
public string? Name {get; set;}
}
В выражении person?.Name = "Tom" правая часть оператора присвоения = вычисляется только тогда, когда левая часть не равна null. Если переменная person равна null, присвоения не будет.
Причем та же логика присваивания с проверкой на null применяется и с составными операторами присваивания (+=, -= и другими). Однако инкремент и декремент (++ и --) не допускаются.
Приложения на основе файлов
Начиная с C# 14 и .NET 10 можно создавать приложения на основе файлов (file-based app). Это упрощает сборку и запуск программ на C#.
Теперь для создания и запуска программы на C# достаточно определить файл с кодом на C# и передать его команде команды dotnet run. Например, определим файл hello.cs с каким-нибудь простеньким содержимым:
Console.WriteLine("Hello METANIT.COM");В консоли перейдем в папке, где располагается данный файл, и выполним команду dotnet run hello.cs
eugene@Eugene:/dotnet/scripts$ dotnet run hello.cs
Hello METANIT.COM
eugene@Eugene:/dotnet/scripts$