.NET Разработчик
6.53K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День четыреста пятьдесят шестой. #CSharp9
Позиционное и Номинальное Создание Объектов. Начало
C# позволяет создавать объекты, используя позиционный или номинальный стиль.
- Позиционное создание предполагает использование конструктора для задания значений свойствам:
public class Person {
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName) {
FirstName = firstName;
LastName = lastName;
}
}
Person p = new Person("Charles", "Leclerc");
При наследовании для инициализации унаследованных свойств конструктор может вызывать конструктор базового класса.

- Номинальное создание предполагает использование инициализаторов:
Person p = new Person { FirstName = "Charles", LastName = "Leclerc" };

До сих пор проблема с инициализаторами была в том, что таким образом можно было задавать значения только доступным для записи свойствам. Инициализатор объекта - это просто синтаксический сахар. За кулисами он устанавливает значения свойствам после вызова конструктора. Таким способом невозможно создать неизменяемые типы. Поэтому часто приходится создавать конструкторы и использовать позиционный стиль.
См. также «Использование Неизменяемых Структур Данных в C#»

Номинальное Создание в C# 9
В C# 9 для создания неизменяемых типов предложен новый вид свойств только для инициализации с использованием ключевого слова init. Эти свойства могут быть установлены после выполнения конструктора, используя инициализатор объекта:
public class Person {
public string FirstName { get; init; }
public string LastName { get; init; }
}
Person p = new Person { FirstName = "Charles", LastName = "Leclerc" };

Свойства только для инициализации могут быть установлены в момент создания объекта, но становятся доступными только для чтения после завершения создания объекта. Когда требуется проверка при задании свойства, ключевое слово init может использоваться для определения блока проверки, так же как get и set. Кроме того, иногда может требоваться более сложная проверка, затрагивающая комбинацию нескольких свойств. В таком случае можно использовать новый валидатор с блоком кода, обозначенным ключевым словом init:
public class Person {
public string FirstName { get; init; }
public string LastName { get; init; }

init {
if (FirstName.Length + LastName.Length > 52)
throw new Exception("…");
}
}

Окончание следует…

Источник:
https://csharp.christiannagel.com/2020/04/21/c-9-positional-or-nominal-creation/
👍1
День четыреста пятьдесят седьмой. #CSharp9
Позиционное и Номинальное Создание Объектов. Окончание
Фабричный Метод и Выражение With
Для создания новых объектов из существующих предложено использовать конструкторы копий и фабричный метод или выражение With. В следующем фрагменте кода класс Person определяет конструктор копии, который возвращает новый объект Person. Метод With помечается как фабричный и вызывает конструктор копии. В классе Racer, производном от Person, определяется конструктор копии, который, в свою очередь, вызывает конструктор копии базового класса. Также переопределяется метод With базового класса для возврата объекта Racer.
public class Person {
public string FirstName { get; init; }
public string LastName { get; init; }

protected Person(Person p) =>
(FirstName, LastName) = (p.FirstName, p.LastName);
[Factory] public virtual Person With() =>
new Person(this);
}

public class Racer : Person {
public string RacingTeam { get; init; }

protected Racer(Racer r) : base(r) =>
RacingTeam = r.RacingTeam;
[Factory] public override Racer With() =>
new Racer(this);
}
Таким образом, можно создавать новые экземпляры со свойствами только для чтения. А поскольку метод With является фабричным, инициализатор объекта может быть использован для внесения некоторых изменений:
Person p1 = new Racer { FirstName = "Charles", LastName = "Leclerc", RacingTeam = "Ferrari" };
Person p2 = p1.With() { FirstName = "Arthur" };

Кроме того, предложено использовать выражение with вместо вызова фабричного метода:
Person p3 = p1 with { FirstName = "Arthur" };

Записи
Вместо определения конструктора копии и метода With также предлагается объявлять новый тип объектов – записи, используя ключевое слово record. В этом типе объектов будет автоматически создан конструктор копии, метод With, методы проверки на равенство и т.п.:
public record class Person {
public string FirstName { get; init; }
public string LastName { get; init; }
}
public record class Racer : Person {
public string RacingTeam { get; init; }
}
Person p1 = new Racer { FirstName = "Charles", LastName = "Leclerc", RacingTeam = "Ferrari" };
Person p2 = p1 with { FirstName = "Arthur" };

Источник: https://csharp.christiannagel.com/2020/04/21/c-9-positional-or-nominal-creation/
День пятьсот седьмой. #CSharp9
C# 9: На Пути к Поддержке Сценариев
Одной из определяющих характеристик языков «сценариев» является то, что им не нужен шаблон. Самой первой строкой файла могут быть объявления и операторы. Тогда как в C# или Java требуется некоторый метод Main, содержащийся внутри класса.

Мэдс Торгерсен из Microsoft предлагает реализовать эту возможность в C# 9. Операторы и функции верхнего уровня можно будет не включать в метод Main класса Program.

Компилятор C # в настоящее время понимает диалект языка, используемого для различных сценариев и интерактивных целей. На этом диалекте операторы могут быть написаны на верхнем уровне (без включения в тело методов), а невиртуальные члены могут быть написаны на верхнем уровне (без включения в объявление типа).

В настоящее время версия C# для сценариев используется не очень интенсивно, но Торгерсен предсказывает, что это изменится в будущем: «Помимо Try.NET, сценарии также набирают популярность в обработке данных и машинном обучении, и там сценарные языки выигрывают от непосредственного режима работы с живыми данными.»

Есть несколько сценариев реализации этого функционала, наиболее вероятный из которых следующий. Операторы будет разрешено размещать перед объявлением пространства имен. Любые такие операторы будут скомпилированы в функцию Main класса Program. Эта функция будет поддерживать асинхронные операции. Если несколько файлов имеют исполняемые операторы вне пространства имен, произойдет ошибка компилятора.

Содержимое операторов будет определять, как будет выглядеть сгенерированный код. Существует четыре возможности в зависимости от того, используется ли await или нет, и есть ли оператор return:
static void Main(string[] args)
static int Main(string[] args)
static Task Main(string[] args)
static Task<int> Main(string[] args)

Локальные функции разрешены с использованием обычного синтаксиса.

Другой сценарий. Функции могут быть объявлены в пространстве имен или глобально. По умолчанию они будут internal, хотя public также будет разрешён. Потребители увидят, что функция принадлежит непосредственно к пространству имен. Реализовано это будет через частичный класс, оборачивающий элементы как статические члены. Если какой-либо из членов верхнего уровня будет public, то и класс будет public и помечен таким образом, чтобы потребляющая сборка знала, что к членам этого класса можно будет обращаться напрямую.

Источник: https://www.infoq.com/news/2020/05/CSharp-9-Scripting/
День пятьсот девятый. #CSharp9
C# 9: Улучшения Частичных Методов для Генераторов Кода
Генераторы исходного кода в C# 9 позволят расширениям компилятора проверять код, а затем вводить дополнительный исходный код во время компиляции. Этот внедрённый код затем включается в ту же компилируемую сборку. Чтобы упростить эту возможность, Microsoft снимает большинство ограничений с частичных методов.

Частичные методы были введены в 2007 году, чтобы расширить возможности генераторов исходного кода. Основная идея в том, что в сгенерированном коде есть заглушки в виде частичных методов. Если разработчик хочет изменить поведение кода, то он может реализовать частичный метод. В противном случае частичный метод, как и любые его вызовы, удаляются.

Теперь поведение изменено. Разработчик определяет, какие частичные методы нужны. Затем генератор исходного кода видит и реализует их, используя комбинацию внешней информации и информации об окружении. Например, генератор исходного кода может комбинировать атрибуты частичного метода со схемой базы данных для генерации SQL кода запроса.

В настоящее время частичные методы имеют несколько ограничений:
- должны возвращать void,
- могут иметь параметры in или ref, но не out,
- неявно являются закрытыми, и поэтому не могут быть виртуальными,

Согласно предложению о расширении частичных методов, эти ограничения будут сняты. Причина этого изменения объясняется так: «Это расширило бы набор сценариев использования генераторов кода, в которых могли бы участвовать частичные методы. Например, регулярное выражение может быть определено с использованием следующего шаблона:
[RegexGenerated("(dog|cat|fish)")]
public partial bool IsPetMatch(string input);
Это даёт разработчику простой декларативный способ использования генераторов кода, а генераторам очень простой набор инструкций для генерации вывода.»

При этом получится вторая категория частичных методов, которые не будут удаляться из кода, но не будут ограничены в том, что вы можете с ними делать. Чтобы различать две категории, предлагается два основных правила:
1. Если partial указан с явным модификатором доступа (public, private и т.д.), то метод должен быть реализован, может возвращать значения и может иметь параметры out.
2. Если partial указан без явного модификатора доступа, то метод может быть удалён из кода, должен возвращать void и не может иметь out параметров.

Чисто технически эта новая категория частичных методов представляет собой абстрактные методы, потому что они должны быть реализованы. Но поскольку такие методы не обязательно являются виртуальными, в Microsoft решили, что перепрофилирование ключевого слова abstract только внесёт лишнюю путаницу.

Другие члены класса, такие как частичные свойства, частичные конструкторы и частичные операторы, находятся на рассмотрении для C# 10. Поскольку они не являются строго необходимыми для расширения возможностей генераторов кода, вводить их в C# 9 не является критически важным.

Источник: https://www.infoq.com/news/2020/06/CSharp-9-Partial-Methods/
День пятьсот одиннадцатый. #CSharp9
C# 9: Упрощённая Проверка Параметров на Null
Эта функция конкурировала с несколькими другими предложениями: атрибуты, флаги компилятора и т.п. Все они были отброшены в пользу узкоспециализированного предложения, которое уменьшает объем кода проверки параметра на null до одного символа.

Оператор ! может быть расположен после любого идентификатора в списке параметров, что заставит компилятор C# выдать стандартный код проверки на null для этого параметра:
void M(string name!) {…}
будет скомпилировано как:
void M(string name) {
if (name is null)
throw new ArgumentNullException(nameof(name));

}

Правила довольно очевидны:
1. Проверки на null вводятся в начале функции перед любым другим кодом и выполняются в том же порядке, что и параметры в сигнатуре функции.
2. Выполняется именно проверка на равенство null, игнорируя любые перегрузки оператора ==.
3. В конструкторе проверка произойдет после вызова конструктора базового класса. В Microsoft рассматривали возможность проверки параметров перед вызовом конструктора базового класса. Это всегда было разрешено в .NET и, возможно, это предпочтительнее. Тем не менее, синтаксис C# не позволяет этого, и было решено не генерировать код, который не может быть воссоздан без использования этой функции.
4. В итераторе проверка всегда выполняется при первоначальном вызове.
5. Ошибки компиляции возникнут в случаях:
- проверки параметра, который не может быть null (например, struct, unmanaged),
- проверки параметра метода без реализации (например, абстрактного или частичного метода, делегата, метода интерфейса),
- проверки out параметра.
6. Предупреждения компилятора возникнут в случаях:
- Проверки явно обнуляемого параметра (string? x!). Предположительно это не приводит к ошибке, чтобы позволить подклассу быть более строгим, чем базовый класс.
- Проверки необязательного параметра со значением null по умолчанию.

Предполагаются следующие сценарии для дженериков:
void M<T>(T value!) { } // OK
void M<T>(T value!) where T : struct { } // ошибка
void M<T>(T value!) where T : unmanaged { } // ошибка
void M<T>(T value!) where T : notnull { } // OK
void M<T>(T value!) where T : class { } // OK
void M<T>(T value!) where T : SomeStruct { } // ошибка
void M<T>(T value!) where T : SomeClass { } // OK

Недостаток этого подхода в том, что он не поддерживает проверку свойств, поскольку параметр value не задаётся явно, а только подразумевается. Пользователи предложили обходной путь, в котором оператор ! применяется к ключевому слову set:
public Foo Foo {get; set!;}
public Bar Bar {
get { return bar; }
set! { bar = value; DoSomethingElse(); }
}

Источник: https://www.infoq.com/news/2020/06/CSharp-9-Null/
День пятьсот тридцать девятый. #ЧтоНовенького #CSharp9
C#9: Новые Ключевые слова and, or и not для Сопоставления по Шаблону
Хотя это может звучать как первоапрельская шутка, в C#9 хотят добавить and, or и not в список ключевых слов для использования в сопоставлении по шаблону.

Чтобы сделать сопоставление по шаблону более гибким и мощным, хотят добавить концепцию конъюнктивных, дизъюнктивных и отрицательных шаблонов. Внешне они выглядят как логические операции, в которых вы хотите выполнить сопоставление с обоими шаблонами (конъюнктивный), с любым из шаблонов (дизъюнктивный), либо не совпадающее с шаблоном (отрицательный).

В этом и проблема. Если вы работаете с логическими значениями, то операторы && и || будут неоднозначными. Компилятор не сможет определить, относятся они к значениям или к шаблонам. Чтобы проиллюстрировать эту идею, рассмотрим дизъюнктивный шаблон:
if (myBool is true or false)
Это будет интерпретировано как «истина, если myBool равно true или если myBool равно false».

Если бы мы использовали операторы && и || для объединения шаблонов, получился бы следующий код:
if (myBool is true || false)
Но это буквально означает «истина, если myBool равняется результату логического выражения (true или false)», что можно упростить до «истина, если myBool равняется true». А это совершенно не то, что мы хотели бы получить в дизъюнктивном шаблоне.

Поэтому необходимы новые ключевые слова, чтобы избежать двусмысленности. Подробнее они обсуждаются в этом предложении на GitHub.

Источник: https://www.infoq.com/news/2020/07/CSharp-And-Or-Not/
День пятьсот сорок второй. #ЧтоНовенького #CSharp9
C#9: Операторы Диапазона в Конструкциях Switch и Сопоставлениях по Шаблону

С момента первого выхода C# разработчики жаловались на отсутствие операторов диапазона в конструкциях switch. Как часть улучшений сопоставлений по шаблону в C#9, это ограничение было устранено.
Следующие шаблоны будут разрешены после ключевых слов case или is:
- < константа
- > константа
- <= константа
- >= константа

Шаблоны всё ещё ограничены только константами. Это становится проблемой, когда вы имеете дело с датой, временем или другими аналогичными структурами, поскольку они не имеют константного представления в C#.

В обсуждении предложения на GitHub Нил Гафтер написал по этому поводу:
«Смысл этой функциональности был в расширении сопоставления по шаблону. В частности, ограничение только константами сделано, чтобы компилятор мог проанализировать и оптимизировать весь набор операций сопоставления по шаблону. Если компилятор не знает значения, с которым он сопоставляет, он не сможет этого сделать. В конце концов, чем плохи обычные выражения?»

Мариуш Павельски отвечает, почему ограничение может стать проблемой:
«Я просто думаю, что будет много людей, которые захотят использовать ключевые слова is, and и or просто как ещё один способ написания логических выражений. Ещё одна небольшая функция C#, которая делает код более лаконичным. Они не будут знать, что это часть большой функциональности сопоставления по шаблону, которая «была разработана для работы с константами». Они будут просто сбиты с толку, когда вместо константы будут использовать имя переменной и получат ошибку «CS0150: A constant value is expected» (Ожидается константное значение).»

Как бы то ни было, операторы and, or и not также могут быть использованы в сочетании с диапазонами. Например,
bool IsLetter(char c) =>
c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';


Это немного трудно читать, поэтому в выражениях можно использовать круглые скобки:
bool IsLetter(char c) =>
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');


Источник: https://www.infoq.com/news/2020/07/CSharp-9-Range-Patterns/
День пятьсот сорок пятый. #ЧтоНовенького #CSharp9
C#9: Незначительные Улучшения в Лямбдах
В C#9 лямбда-функции получат два обновления. Ни одно из них не изменит способ написания кода, но они помогут разработчику прояснить свои намерения.

Дискард-Параметры Лямбда Функций
Позволят разработчикам явно указать, что некоторые параметры не нужны, что предотвратит ошибочные предупреждения компилятора о неиспользуемых параметрах. Это может быть полезно, например, в обработчиках событий, когда не нужны параметры sender и eventArgs:
button1.Click += (s, e) => ShowDialog();
Замена параметров, как показано ниже, позволит явно указать, что они не используются:
button1.Click += (_, _) => ShowDialog();
При необходимости могут быть использованы типы:
var handler = (object _, EventArgs _) => ShowDialog();

Статические Анонимные Функции
Будут использоваться, чтобы обозначить, что лямбда или анонимная функция не может захватывать локальные переменные (включая параметры). Вот пример из оригинального предложения функционала:
int y = 10;
someMethod(x => x + y);
//захватывает 'y', приводя к неявному выделению памяти

Проблема в том, что это неявно приводит к увеличению времени жизни локальной переменной, т.к. анонимная функция может существовать дольше, чем окружающий её метод.
Чтобы избежать случайного захвата любого локального состояния при предоставлении лямбда-функций в качестве аргумента метода, предлагается добавить к лямбда-объявлению ключевое слово static. Это сделает лямбда-функцию похожей на статический метод, который не может захватывать локальные объекты и не имеет доступа к this или base:
int y = 10;
someMethod(static x => x + y); //ошибка!

Чтобы исправить эту ошибку, переменная y должна быть изменена на константу или статическое поле:
const int y = 10;
someMethod(static x => x + y);

Вот основные правила для статических анонимных функций:
- может ссылаться на статические члены из окружающего кода;
- может ссылаться на определения констант из окружающего кода;
- не может захватывать состояние из окружающего кода, т.е. локальные объекты, параметры и this из окружающего кода недоступны;
- не может ссылаться на экземплярные члены окружающего её класса;
- nameof() внутри статической анонимной функции может ссылаться на локальные объекты, параметры, this или base из окружающего кода.

Источник: https://www.infoq.com/news/2020/07/CSharp-Lambdas/
День пятьсот шестьдесят седьмой. #ЧтоНовенького #CSharp9
Первичные Конструкторы в C#9
В C#9 представлен новый упрощённый способ создания классов. Большинство конструкторов очень избыточны, поскольку аргументы конструктора чаще всего используются только для инициализации свойств с тем же именем. Теперь есть способ избавиться от этого лишнего кода. Сейчас мы пишем что-то вроде этого:
public class Person {
public Person(string firstName, string lastName) {
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}
8 строк кода (11, если переносить открывающую скобку на новую строку), только чтобы создать простой POCO (Plain Old CLR Object). Теперь это можно сделать в одну строку:
public record Person(string firstName, string lastName);
На момент написания первичные конструкторы доступны только для записей. Классы и структуры пока не поддерживаются, но должны быть реализованы в окончательной версии.

Инициализаторы
Первичные конструкторы также используют новые init-свойства. Это означает, что мы можем установить значение свойства с помощью инициализатора объекта:
var p = new Person("Miguel", "Bernard") { lastName = "test" };
Это работает, как и ожидалось. После выполнения конструктора значение "Bernard" заменяется на "test". Однако вы не можете использовать инициализатор объекта для задания обязательных свойств первичного конструктора:
// Это не скомпилируется
var p = new Person("Miguel") { lastName = "test" };

Значения по умолчанию
В первичный конструктор можно передавать значения по умолчанию:
public record Person (string firstName, string lastName = "lastName");
var p = new Person("Miguel");

Я вижу большой потенциал в этой новой функции, особенно для POCO/DTO и простых объектов. Удаление всего этого бесполезного шаблонного кода сократит издержки при создании новых типов и упростит кодовую базу.

Источник: https://blog.miguelbernard.com/c-9-0-primary-constructors/
👍2
День пятьсот шестьдесят восьмой. #ЧтоНовенького #CSharp9
Подробнее про Записи в C#9
Записи в C#9 помогут нам создавать неизменяемые типы, которые очень полезны в большой распределенной архитектуре. Поскольку запись ведет себя иначе, чем класс или структура, Microsoft ввели новое ключевое слово record.
public record Chicken { public string Name {get;init;} }
var c = new Chicken { Name = "test" };
// c.Name = "t"; - изменение значения недопустимо

Первичные конструкторы
Ещё интереснее использование записей с первичными конструкторами:
public record Chicken(string name);
Фактически есть большой соблазн использовать только первичные конструкторы для объявления записей, чтобы отличать их от объявления классов.

With
С введением записей добавится ещё одно ключевое слово with, которое позволит «клонировать» запись, изменив только некоторые свойства.
var c2 = c with { Name = "myNewName" };

Структурное равенство
Записи обладают структурным равенством, то есть две записи считаются равными, если все их свойства равны. Такое поведение сложно реализовать с помощью классических классов, поскольку нужно переопределить методы Equals и GetHashCode и обновлять код этих методов при изменении свойств.

Деконструкция
Записи также поддерживают деконструкцию, что позволяет неявно преобразовать запись в кортеж, содержащий все свойства:
public record Decons(int qty, string name, DateTime time);
var r = new Decons(42, "name", DateTime.Now);
var(x, y, z) = r;
Того же результата можно достичь с помощью метода Deconstruct(), доступного для всех типов записей.

Наследование
Поскольку C# - объектно-ориентированный язык, наследование является частью ООП, и его следует поддерживать. Должно быть, это самая серьёзная проблема, которую пришлось преодолеть команде C#, - сделать записи частью языка, сохранив при этом обратную совместимость.
public abstract record Food (int Cal);
public record Milk(int C, double Fat) : Food(C);
var m = new Milk(1, 3.25);
При этом можно обратиться как к m.C, так и к m.Cal (они будут равны 1). Но что, если объявить свойства одинаково?
public record Milk(int Cal, double Fat) : Food(Cal);
У вас останется одно свойство Cal, причём оно будет наследовано от Food. Думаю, это наиболее логичное поведение.
Заметьте, что невозможно смешивать классы и записи в цепочке наследования (наследовать класс от записи или наоборот). Однако, вполне возможно, чтобы запись реализовывала интерфейс:
public interface IRecord { }
public record RecordType : IRecord { }

Ограничения обобщений
На момент написания невозможно использовать ключевое слово record для ограничения обобщения:
public void Method<T>(T t) where T : record
Надеюсь, к моменту выхода 9й версии это будет поддерживаться.

Источник: https://blog.miguelbernard.com/c-9-0-records/
👍3
День шестьсот пятый. #ЧтоНовенького #CSharp9
C#9: Улучшенное Сопоставление с Образцом
В своё время VB стал популярным из-за своей выразительности. Программу можно было читать почти как обычный английский текст. Для сравнения, C# и C++ оптимизированы для краткости синтаксиса и производительности. Это сделало их быстрыми, но в некоторых случаях трудным для чтения. Сравните VB:
If Not a And b Then

Else

EndIf

И C#:
if(!(a) && b)
{

}
else
{

}

Это простой пример, но вы можете представить, что люди создают намного более сложные логические выражения, содержащие множество символов.

C#9 теперь поддерживает несколько новых ключевых слов: not, and и or. Это упрощает чтение некоторых выражений, но вы по-прежнему можете использовать более короткий синтаксис с использованием символов:
if(s is not string)
на мой взгляд, проще для чтения, чем
if(!(s is string))

Сопоставление с образцом
Эти новые операторы полезны при сопоставлении с образцом. Кстати, теперь вы также можете использовать реляционные шаблоны, такие как >=, > и т.д., непосредственно в выражениях:
record Person(int? Weight);
Person p = new Person(175);
var category = p.Weight switch
{
< 150 => "лёгкий",
>= 150 and < 200 => "средний",
not null => "неизвестно",
null => "ошибка"
};

До C#9 некоторые шаблоны было невозможно выразить, например, not null. В основном это было ограничением компилятора, потому что !null не был допустимым синтаксическим токеном в C#. То же самое относится к таким выражениям, как
>= 150 and < 200
До C#9 компилятор не знал, как анализировать &&, за которым следует другой логический символ, например <. В выражении сопоставления с образцом компилятор знает, с какой переменной мы сопоставляем, и понимает, что означает это выражение, без повторяющихся объявлений переменных, которые только загромождали выражение. В итоге мы получили синтаксис, который намного проще понять людям, читающим код.

Источник: https://blog.miguelbernard.com/c-9-0-improved-pattern-matching/
День шестьсот сорок четвёртый. #ЧтоНовенького #CSharp9
C# 9 Неизвестные Фишки
В C# 9 появилось много новых возможностей (см. посты по тегу #CSharp9) и несколько очень полезных малоизвестных вещей.

Инициализаторы модулей
Этот серьёзный недостаток, который до сих пор можно было исправить только некоторыми сторонними инструментами, теперь поддерживается в .NET. Инициализаторы модулей позволят библиотекам выполнять инициализацию при загрузке с минимальными накладными расходами и без необходимости явного вызова со стороны пользовательского кода. В основном это встречалось в библиотеках тестов, где вы хотите что-то инициализировать перед запуском одного, нескольких или даже всех тестов в сборке. Например, его можно использовать для переопределения некоторых статических переменных перед запуском тестирования. Чтобы использовать эту функцию, пометьте метод атрибутом ModuleInitializer. Не все методы могут поддерживать эту функцию. Метод должен:
- быть статическим
- без параметров
- возвращать void
- не быть обобщённым
- не содержаться в обобщённом классе
- быть доступным из содержащего его модуля (internal или public).
[ModuleInitializer]
public static void Magic() { … }

Упрощенная проверка на null
Для реализации проверок на null требуется много шаблонного кода и хорошая дисциплина для их последовательного применения. C# 9 решает эту проблему, предоставляя упрощённый синтаксис. Просто добавьте ! в конце имени параметра метода:
public void Before(string name) {
if (name is null)
throw new ArgumentNullException();
}
Следующий метод приведёт к аналогичному сгенерированному IL-коду:
public void Now(string name!) { }

Ковариантные Переопределения
Ковариантные переопределения теперь позволяют объявлять более конкретный тип при переопределении метода базового класса, содержащего менее конкретный тип возврата:
abstract class Animal {
public abstract Food GetFood();
}
class Tiger : Animal {
public override Meat GetFood() => …;
}

Это значительное улучшение, поскольку теперь вы можете избежать приведения возвращаемого значения во время выполнения при использовании дочернего типа:
var t = new Tiger();
// Раньше
Meat m = (Meat)t.GetFood();
// Сейчас
Meat m2 = t.GetFood();

Int Нативного Размера
Это новая конструкция, которая позволяет объявлять int, размер которого определяется платформой: 32 или 64 бита. nint и nuint - новые ключевые слова для этого.

Такая возможность уже существовала с IntPtr и UIntPtr. nint и nuint - это просто оболочки над этими типами, предоставляющие дополнительные возможности, такие как преобразование и арифметические операции, которые невозможны с IntPtr. Это может значительно оптимизировать производительность приложения при выполнении интенсивных вычислений, требующих низкоуровневого доступа и где важен каждый байт.

Источник: https://blog.miguelbernard.com/c-9-the-unknown-goodies/
День шестьсот шестидесятый. #ЧтоНовенького #EFCore5
Вместе с .NET 5 выпущено множество обновлений. Про новшества в C#9 я писал в постах с тегом #CSharp9. Теперь рассмотрим, что нового в Entity Framework Core 5.0.

Отношение многие-ко-многим
EF Core 5.0 поддерживает отношения многие-ко-многим без явной привязки вспомогательной таблицы. Рассмотрим следующие сущности постов в блоге и тегов:
public class Post {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag {
public int Id { get; set; }
public string Text { get; set; }
public ICollection<Post> Posts { get; set; }
}

Заметьте, что Post содержит коллекцию элементов Tag и наоборот. EF Core 5.0 по соглашению распознает это как отношение многие-ко-многим. То есть добавлять специальный код в OnModelCreating не требуется. Когда для создания базы данных используются миграции (или EnsureCreated), EF Core автоматически создаст вспомогательную таблицу. Например, в SQL Server для этой модели EF Core сгенерирует:
CREATE TABLE [Posts] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([Id])
);
CREATE TABLE [Tag] (
[Id] int NOT NULL IDENTITY,
[Text] nvarchar(max) NULL,
CONSTRAINT [PK_Tag] PRIMARY KEY ([Id])
);
CREATE TABLE [PostTag] (
[PostsId] int NOT NULL,
[TagsId] int NOT NULL,

);

Создание и связывание экземпляров объектов Tag и Post приведёт к автоматическому обновлению вспомогательной таблицы. После вставки сообщений и тегов EF автоматически создаст строки во вспомогательной таблице. Для запросов Include и другие операции будут работать так же, как и для любых других отношений.

В отличие от EF6, EF Core также позволяет полностью настраивать вспомогательную таблицу. Например, приведенный ниже код настраивает отношение «многие-ко-многим» через вспомогательный объект, в котором вспомогательный объект также содержит свойство с полезными данными (PublicationDate):
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder
.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany()
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany()
.HasForeignKey(pt => pt.PostId),
j => {
j.Property(pt => pt.PublicationDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}

Источник: https://docs.microsoft.com/ru-ru/ef/core/what-is-new/ef-core-5.0/whatsnew#many-to-many
День шестьсот шестьдесят шестой. #ЗаметкиНаПолях #CSharp9
Атрибуты для Свойств Записей в C# 9
Записи обеспечивают простое создание неизменяемых объектов, особенно при использовании первичного конструктора:
public record User(string Name, DateTime DOB);
Это значительно сокращает код. Рассмотрим ситуацию, когда вы хотите сериализовать запись, чтобы получить следующий результат:
{"User":"Jon Smith","DateOfBirth":"1970-01-01T00:00:00"}
Заметьте, что ключи не совпадают с именами свойств записи. Обычно это потребовало бы добавления к свойствам записи атрибута JsonPropertyAttribute. То есть в полной записи это выглядело бы так:
public record User {  
[JsonProperty("User")]
public string Name{get;init;}

[JsonProperty("DateOfBirth")]
public DateTime DOB{get;init;}
}

При использовании первичного конструктора есть соблазн сделать аналогично:
public record User(
[JsonProperty("User")]
string Name,
[JsonProperty("DateOfBirth")]
DateTime DOB
);

Но это неверно. В этом случае атрибуты добавятся к параметрам конструктора, а не к свойствам.

Решение заключается в указании цели, к которой применяется атрибут. Как сказано в документации Microsoft:
Атрибуты могут быть применены к синтезированному автоматическому свойству или его вспомогательному полю, используя указатель цели атрибута property: или field: соответственно для атрибутов, синтаксически применяемых к соответствующему параметру записи.

В итоге мы получим следующую запись:
public record User(
[property:JsonProperty("User")]
string Name,
[property:JsonProperty("DateOfBirth")]
DateTime DOB
);

Теперь можно сериализовать нашу запись и получить желаемый результат:
var data = new User("Jon Smith",new DateTime(1970,1,1));  
var serializedData = JsonConvert.SerializeObject(data);

// Вывод
{"User":"Jon Smith","DateOfBirth":"1970-01-01T00:00:00"}

Источник: https://www.c-sharpcorner.com/blogs/attributes-for-record-properties-in-c-sharp-9
День шестьсот восьмидесятый. #ЧтоНовенького #CSharp9
Ещё Раз про Сопоставления с Образцом
Я уже писал об изменениях в сопоставлении с образцом в C#9. Здесь же хочу привести некое саммари всех изменений с примерами:

1. Шаблоны типа используются для сопоставления с типом. Если тип входных данных соответствует типу, указанному в шаблоне, совпадение считается успешным.
object checkType = new int();
var getType = checkType switch {
string => "string",
int => "int",
_ => "obj"
};
Console.WriteLine(getType);
// Вывод: int

2. Реляционные шаблоны позволяют сопоставить входные данные с константами, используя знаки >, < или = (a также >= или <=):
var person = new Person("John", 42);
var person2 = new Person("Jane", 8);
var ageInRange = person switch {
//тип Person указан явно
Person(_, < 18) => "меньше 18",
//тип выводится компилятором
(_, > 18) => "больше 18",
(_, 18) => "18!"
};
Console.WriteLine(ageInRange);
// Вывод: больше 18

3. Комбинаторные шаблоны позволяют комбинировать несколько шаблонов в одной строке:
var person = new Person("John", 42);

- Конъюнктивные представляют собой логическое «и» двух подшаблонов:
var ageInRange = person switch {
(_, < 18) => "меньше 18",
("John", _) and (_, > 18) => "Джону больше 18"
};
Console.WriteLine(ageInRange);
// Вывод: Джону больше 18

- Дизъюнктивные представляют собой логическое «или» двух подшаблонов:
var ageInRange = person switch {
(_, < 18) => "меньше 18",
(_, 18) or (_, > 18) => "18 или больше"
};
Console.WriteLine(ageInRange);
// Вывод: 18 или больше

- Отрицательные требуют несовпадения с заданным шаблоном:
var isJohn = person switch {
not ("John", 42) => "не Джон!",
_ => "Джон :)"
};
Console.WriteLine(isJohn);
// Вывод: Джон :)

4. В шаблонах допустимо использовать скобки:
public record IsNumber(bool IsValid, int Number);
var num = new IsNumber(true, 10);
var zeroToTen = num switch {
((_, >= 0 and <= 5) or (_, > 5 and <= 9))
or (_, 10) => "от 0 до 10",
_ => "больше 10"
};
Console.WriteLine(zeroToTen);
// Вывод: от 0 до 10

Источник: https://www.c-sharpcorner.com/article/c-sharp-9-cheatsheet/