.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День четыреста тридцать седьмой. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Начало
Традиционно во многих веб-средах URL-адрес представлял собой физический файл на диске. Запрос к URL принимался веб-сервером, который выполнял некоторый код в этом файле для получения ответа. В MVC в большинстве случаев используется другой подход - отображение URL-адреса на вызов метода в классе.
Система маршрутизации работает с использованием множества маршрутов. Вместе маршруты образуют схему URL приложения, представляющую собой набор URL, которые приложение будет распознавать и реагировать на них. Каждый маршрут содержит шаблон URL, с которым сравниваются входящие URL. Если входящий URL соответствует шаблону, тогда он применяется системой маршрутизации для обработки этого URL. URL могут быть разбиты на сегменты - части URL кроме имени хоста и строки запроса, которые отделяются друг от друга символом /. Например, https://mysite.com/Admin/Index содержит два сегмента Admin и Index.

Подходы маршрутизации
Централизованный стиль определения маршрутов в одном месте называется традиционной маршрутизацией или маршрутизацией на основе соглашений. Начиная с MVC 5, есть другой вариант, использующий атрибуты контроллеров и методов действий, который называется маршрутизацией с помощью атрибутов.

При использовании маршрутизации с помощью атрибутов маршрут определяется отдельно для каждого контроллера или даже для каждого метода действия, с помощью атрибута Route.
[Route("home/[action]")]
public class HomeController : Controller {…}
Кроме того, с помощью атрибутов можно переименовывать действия в контроллере:
[Route("[controller]/MyAction")]
public ViewResult Index() …
Тогда метод действия Index можно будет вызвать только по пути /<имя контроллера>/MyAction.
К маршрутам, заданным в атрибутах, можно применить все те же расширения и ограничения, как и к традиционным. Они будут рассмотрены далее.

Традиционные маршруты определяются:
- в .Net Framework - в методе RegisterRoutes файла App_Start/RouteConfig.cs:
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("", "{controller}/{action}");
}
- в .Net Core - в методе Configure файла Startup.cs:
public void Configure(IApplicationBuilder арр, IHostingEnvironment env) {

app.UseMvc(routes => { routes.MapRoute("", "{controller}/{action}");
}
Принцип определения маршрутов похож. Суть сводится к добавлению маршрута в коллекцию, для чего удобнее всего использовать метод MapRoute:
routes.МapRoute(name: "default", template: "{controller}/{action}");

Использовать традиционные маршруты или атрибуты зависит от ваших предпочтений. Обычно традиционные маршруты используют для существующих приложений, когда хотят:
- Иметь централизованную настройку всех маршрутов.
- Использовать пользовательские объекты ограничений.
Маршруты с использованием атрибутов имеет смысл использовать для новых приложений, если вы хотите хранить маршруты вместе с кодом методов действий.
Оба типа можно использовать совместно. На практике маршруты, заданные атрибутами, обычно более специфичны, поэтому в .Net Framework рекомендовано расположить вызов routes.MapMvcAttributeRoutes(); перед определением традиционных маршрутов. В .Net Core этого делать не требуется, маршрут, определённый атрибутом, будет иметь приоритет перед стандартной конфигурацией.

Продолжение следует…

Источники:
- Jon Galloway “Professional
ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro
ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15.
День четыреста тридцать восьмой. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Продолжение
Принципы поведения шаблонов
- Шаблоны URL консервативны в отношении количества сопоставляемых сегментов. Совпадение будет происходить только для тех URL, которые имеют то же самое количество сегментов, что и шаблон.
- Шаблоны URL либеральны в отношении содержимого сопоставляемых сегментов. Если URL имеет правильное количество сегментов, то шаблон извлечет значение каждого сегмента для переменной сегмента, каким бы оно ни было.
Контролировать поведение системы маршрутизации, можно через шаблоны маршрутов. Сначала рассмотрим, как управлять консервативностью количества сопоставляемых сегментов.

1. Статические значения сегментов
- routes.MapRoute(name: "", template: "Public/{controller }/{action }");
URL должен содержать сегмент Public: https://mysite.com/Public/Home/Index
- routes.MapRoute("", "Х{controller}/{action}");
Первый сегмент URL должен начинаться с X, но будет соответствовать контроллеру без X. Например, https://mysite.com/XHome/Index будет соответствовать контроллеру HomeController, методу Index.

2. Определение значений по умолчанию
Любому сегменту можно задать значение по умолчанию, тогда при отсутствии сегмента во входящем URL будет использовано это значение:
routes.MapRoute(name: "", template: "{controller}/{action}", defaults: new {action="Index"});
Также можно использовать встраиваемые значения внутри шаблона:
routes.MapRoute("", template: "{controller=Home}/{action=Index}");
*Если вы определили значение по умолчанию для сегмента, убедитесь, что все сегменты, следующие за ним, также имеют значения по умолчанию. Например, нельзя определить значение по умолчанию для контроллера и не определить его для действия.

3. Специальные переменные сегментов
Можно добавить любое количество сегментов, с именами переменных, которым будут заданы значения из входящего URL:
routes.MapRoute("", "(controller}/{action}/{id=0}");
Имена переменных могут быть любыми, кроме зарезервированных слов controller, action и area. Получить значения этих переменных из метода действия можно либо через словарь RouteData.Values:
var id = RouteData.Values[“id”];
Либо используя привязку модели. Если метод действия определяет параметры с именами, которые совпадают с именами переменных шаблона URL, то инфраструктура MVC будет автоматически передавать методу действия значения, извлеченные из URL, в виде аргументов и попытается привести их к типу аргумента:
public ViewResult List(int id) {…}

4. Несколько переменных в сегменте
Шаблоны URL могут иметь несколько переменных в одном сегменте:
{title}-{artist}
Album{title}and{artist}
{filename}.{ext}
Единственное ограничение – они не должны следовать непосредственно друг за другом, чтобы избежать неоднозначности:
{title}{artist}

5. Необязательные сегменты
Необязательный сегмент может не указываться во входящем маршруте, и тогда он не получит значения. Необязательные сегменты обозначаются знаком ? :
routes.MapRoute("", "(controller}/{action}/{id?}");

6. Маршруты переменной длины
Можно использовать переменную общего захвата (catch-all), которая будет соответствовать неограниченному количеству сегментов. Она обозначается знаком * перед именем:
routes.MapRoute("", "{controller}/{action}/{*catchall}");
В этом случае переменная catchall будет содержать строку со всеми сегментами после второго:
https://mysite.com/Home/Index/1/2/3
var catchall = RouteData.Values[“catchall”]; // “1/2/3”

Продолжение следует…

Источники:
- Jon Galloway “Professional
ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro
ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15.
День четыреста тридцать девятый. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Продолжение
Ограничения маршрутов
Управлять либеральностью при сопоставлении с содержимым сегментов URL можно с помощью ограничений. Ограничения отделяются от имени переменной сегмента двоеточием. Например, здесь допускаются только целочисленные значения для id (либо отсутствие сегмента вообще):
… template: "{controller)/{action)/{id:int?)", …
Также ограничение можно указать за пределами шаблона в параметре constraints метода MapRoute:
routes.MapRoute(name: "",
template: "{controller}/{action}/{id?}",
constraints: new { id = new IntRouteConstraint() });
Классы ограничений определены в пространстве имён Microsoft.AspNetCore.Routing.Constraints и названы в формате <ИмяОграничения>RouteConstraint.

Типы ограничений (по встраиваемому имени)
- alpha – буквы, независимо от регистра,
- bool, datetime, decimal, double, float, int, long – значение, которое может быть преобразовано к соответствующему типу,
- guid – значение GIUD,
- length(len), length(min,max), maxlength(len), minlength(len) – строковое значение заданной длины или заданной максимальной или минимальной длины,
- max(val), min(val), range(min,max) – значение int не больше или не меньше val, или в диапазоне от min до max,
- regex(expr) – соответствует регулярному выражению.

Ограничения можно объединять в цепочки, разделяя двоеточием:
template: "{controller}/{action}/{id:alpha:minlength(6)?}");

Собственные ограничения
Определить собственное ограничение можно, реализовав интерфейс Microsoft.
AspNetCore.Routing.IRouteConstraint
:
public class WeekDayConstraint : IRouteConstraint {
private static string [] Days =
new[] {"mon","tue","wed","thu","fri","sat","sun" };

public bool Match(HttpContext httpContext, IRouter route,
string routeKey, RouteValueDictionary values,
RouteDirection routeDirection) {
return Days.
Contains(values[routeKey]?.ToString().
ToLowerlnvariant());
}
}
Необходимо реализовать метод Match, в который передаются контекст запроса (httpContext), маршрут (route), имя ограничиваемого сегмента (routeKey), словарь переменных сегментов (values) и тип URL – входящий или исходящий – (routeDirection). Теперь ограничение можно использовать в параметре constraints:
… constraints: new { id = new WeekDayConstraint() } …
Либо добавить его во встраиваемые ограничения в методе ConfigureServices класса Startup:
public void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("weekday",typeof(WeekDayConstraint)));

}
и использовать как встроенное ограничение:
… template: "{controller}/{action}/{day:weekday?}" …

Порядок маршрутов
Маршруты применяются в порядке, в котором они определены. Система маршрутизации пытается сопоставить входящий URL с шаблоном URL маршрута,
который был определён первым, и продолжает сопоставление со следующим маршрутом только в случае, если не произошло совпадение. Поэтому самые специфичные маршруты должны быть определены первыми.

Продолжение следует…

Источники:
- Jon Galloway “Professional
ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro
ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15
День четыреста сороковой. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Окончание
Генерация исходящих URL
Система маршрутизации используется и для генерации исходящих URL (например, ссылок на другие страницы). Это делается с помощью помощников HTML в .Net Framework:
@Html.ActionLink("Ссылка", "AnotherAction", "Home", new { id = 100 });
или тэг-помощников в .Net Core:
<а asp-controller="Home" asp-action="AnotherAction" asp-route-id="100">Ссылка</а>
Преимущество такого подхода в том, что можно поменять схему маршрутизации, и ссылки изменятся автоматически.

Система маршрутизации обрабатывает маршруты в порядке их определения на соответствие трём условиям:
1) Доступны значения для всех сегментов шаблона. Проверяются предоставленные значения, значения из текущего запроса и значения по умолчанию маршрута.
*Параметры, не входящие в шаблон маршрута, добавляются к URL в виде строки запроса (…?p1=1&p2=2…), поэтому отсутствие параметра в шаблоне не означает, что он не будет выбран.
2) Ни одно из значений не должно конфликтовать со значениями по умолчанию маршрута. Значение либо должно быть таким же, как значение по умолчанию, либо такого сегмента не должно быть вообще.
3) Значения для всех переменных сегментов должны удовлетворять ограничениям маршрута.
Система маршрутизации не пытается найти лучший маршрут, она находит только первое совпадение, достаточное для генерации URL и использует его.

Ещё одним способом генерации URL является использование имени маршрута и помощников Html.RouteLink или asp-route. Имена маршрутов в системе маршрутизации уникальны, и система сгенерирует URL по схеме того маршрута, имя которого вы зададите.

Конфигурация системы маршрутизации
Можно использовать два булевых свойства системы маршрутизации для настройки URL:
- AppendTrailingSlash – добавлять слеш в конце URL,
- LowercaseUrls – приводить URL к нижнему регистру.
Свойства задаются в методе ConfigureServices класса Startup:
public void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options => {
options.LowercaseUrls = true;
options.AppendTrailingSlash = true;});

}

Советы относительно схемы URL
1. Используйте один формат URL в масштабах всего приложения.
2. Создайте осмысленную иерархию (/Products/Menswear/Shirts/Red), чтобы удаление последнего сегмента приводило в родительскую категорию.
3. URL должны описывать содержимое, а не детали реализации приложения (/Articles/AnnualReport вместо /Website/v2/FromCache/AnnualReport).
4. Лучше использовать описательные URL, а не идентификаторы (/Articles/AnnualReport вместо /Articles/12392). Если без идентификаторов не обойтись, указывайте и номер, и заголовок (/Articles/12392/AnnualReport). Такой URL имеет больше смысла для человека и улучшает поисковый рейтинг. Приложение может просто игнорировать заголовок и отображать элемент, соответствующий идентификатору.
5. Избегайте специальных символов. Для разделения слов используйте дефис (/my-great-article).
6. Не применяйте расширения имен файлов для НТМL-страниц, но задавайте их для скачиваемых файлов (.jpg, .zip, .pdf) для удобства пользователей.
7. Поддерживайте независимость от регистра (поведение по умолчанию).
8. Не изменяйте URL. Если изменение необходимо, создайте постоянное (301) перенаправление со старых адресов на новые.

Источники:
- Jon Galloway “Professional
ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro
ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15
День четыреста сорок первый. #юмор
День четыреста сорок второй. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 1-8.
Новая серия постов про вопросы на собеседовании, на этот раз про ООП. Начнём с основ.

1. Что такое объект?
Определение класса или структуры похоже на схему, которая определяет, что может делать тип. Объект – это, по сути, блок памяти, выделенный и настроенный в соответствии со схемой. Программа может создавать множество объектов одного и того же класса. Объекты также называются экземплярами и могут храниться либо в именованной переменной, либо в массиве или коллекции.

2. Что такое инкапсуляция?
Инкапсуляция - это процесс связывания элементов данных и методов их обработки в одно целое. См. также «Управление сложностью»: начало, окончание.

3. Что такое абстракция?
Абстракция - это использование только тех характеристик объекта, которые с достаточной точностью представляют его в данной системе. Основная идея состоит в том, чтобы представить объект минимальным набором полей и методов и при этом с достаточной точностью для решаемой задачи.

4. Что такое модификаторы (спецификаторы) доступа?
Модификаторы доступа определяют область видимости объекта или члена класса, то есть контекст, в котором можно употреблять данную переменную или метод. См. также «Модификаторы доступа в C#».

5. Что такое наследование?
Наследование - это процесс получения нового класса из уже существующего класса, способствуя повторному использованию компонентов ПО. См. также «Базовые классы для реализации абстракций».

6. Наследуются ли закрытые члены класса?
Да, закрытые члены также наследуются в производном классе, но к ним нельзя получить доступ. Попытка получить доступ к закрытому члену базового класса в производном классе сообщит об ошибке времени компиляции.

7. Что такое полиморфизм?
Полиморфизм - это особенность ООП, позволяющая языку использовать одно и то же имя метода в разных формах. Есть 2 способа достижения этого:
1. Во время компиляции – статический полиморфизм, раннее связывание или перегрузка (overloading).
2. Во время выполнения – динамический полиморфизм, позднее связывание или переопределение (overriding). Подробнее в этом посте.

8. Что такое перегрузка методов?
Перегрузка методов - создание нескольких методов в классе с одним и тем же именем, но с разным количеством или типом параметров. См. также «Советы по перегрузке членов типа».

Источник: https://www.c-sharpcorner.com
День четыреста сорок третий. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
35. Золотое Правило Разработки API
Проектирование API – сложный процесс, особенно для больших систем. Если вы разрабатываете API, которым будут пользоваться сотни или тысячи пользователей, вы должны подумать о том, как вы сможете изменять его в будущем и не сломают ли ваши изменения клиентский код. Кроме того, вы должны подумать, как пользователи вашего API повлияют на вас. Если класс вашего API вызывает один из собственных методов для внутреннего использования, вы должны помнить, что пользователь может наследовать от вашего класса и переопределить этот метод, что может иметь катастрофические последствия. Вы не сможете изменить этот метод, потому что кто-то из ваших пользователей переопределил его для других целей. И ваша внутренняя реализация станет зависеть от ваших пользователей.

Разработчики API решают эту проблему различными способами, самый простой из которых - заблокировать API. У вас может возникнуть желание сделать большинство ваших классов и методов запечатанными. Независимо от того, какой язык вы используете, может возникнуть соблазн представить свой API через синглтон или использовать статические фабричные методы, чтобы защитить API от переопределения поведения или неправильного использования. Всё это кажется разумным, но так ли это на самом деле?

За последнее десятилетие мы постепенно осознали, что модульное тестирование является чрезвычайно важной частью разработки ПО, но это знание ещё не полностью распространилось в отрасли. Подтверждения этому вокруг нас. Возьмите произвольный непротестированный класс, который использует сторонний API, и попробуйте написать для него модульные тесты. В большинстве случаев у вас возникнут проблемы. Вы обнаружите, что код, использующий API, буквально приклеен к классу. Невозможно сымитировать классы API, чтобы протестировать взаимодействие кода класса с ними или подделать возвращаемые значения для проверки.

Со временем ситуация улучшится, но только если при разработке API мы начнём рассматривать тестирование как реальный пример использования. К сожалению, это немного сложнее, чем просто тестирование нашего кода. Вот вам Золотое Правило Разработки API: «Недостаточно написать тесты для разрабатываемого вами API, нужно написать тесты и для кода, использующего ваш API.» Если вы будете так поступать, вы сами будете сталкиваться с проблемами, которые придётся преодолевать вашим пользователям, когда они попытаются протестировать свой код, использующий ваш API.

Нет единого способа облегчить разработчикам тестирование кода, использующего ваш API. Статические и запечатанные классы не являются априори плохими конструкциями. Иногда они могут быть полезны. Но важно знать о проблеме тестирования, и проверить это на себе. После этого вы сможете использовать полученный опыт в проектировании API.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Michael Feathers
День четыреста сорок четвёртый. #MoreEffectiveCSharp
8. Различные Концепции Равенства. Начало
Когда вы создаёте собственные типы (классы или структуры), вы определяете, что означает равенство для этого типа. C# предоставляет четыре метода, которые определяют, «равны» ли два разных объекта:
1.
 static bool ReferenceEquals (object left, object right);
2.
 static bool Equals (object left, object right);
3.
 virtual bool Equals(object right);
4.
 static bool operator ==(MyClass left, MyClass right);

Первые два переопределять не следует. Чаще всего переопределяют экземплярный метод Equals() для обеспечения семантики равенства в типе. Также иногда переопределяется оператор ==, обычно для структур. Между этими четырьмя методами есть взаимосвязь, поэтому при изменении одного вы можете повлиять на поведение других. Кроме того, типы, переопределяющие Equals(), должны реализовывать IEquatable<T>. Типы, которые реализуют семантику сравнения входящих в них элементов (массивы, кортежи), должны реализовывать интерфейс IStructuralEquatable.

1. Object.ReferenceEquals() возвращает true, если две ссылки ссылаются на один и тот же объект. Независимо от того, являются ли сравниваемые типы ссылочными типами или типами значений, этот метод всегда проверяет идентичность объекта, а не его содержимое. ReferenceEquals() всегда возвращает false для значимых типов из-за того, что происходит упаковка значений.

2. Object.Equals() проверяет, равны ли две ссылки, когда вы не знаете тип времени выполнения двух аргументов. Как он это делает? Он делегирует проверку равенства одному из переданных ему параметров. Метод реализован примерно так:
public static bool Equals(object left, object right) {
// проверка ссылочного равенства
if (Object.ReferenceEquals(left, right) )
return true;
// вариант с двумя null учтён выше
if (Object.ReferenceEquals(left, null) ||
Object.ReferenceEquals(right, null))
return false;

return left.Equals(right);
}
Как видите, метод делегирует проверку равенства экземплярному методу Equals() левого аргумента, таким образом используя правила проверки на равенство этого типа.

Ни первый, ни второй метод не следует переопределять, поскольку они и так делают то, что должны.

Прежде, чем обсудить переопределение других двух методов, кратко рассмотрим математические свойства равенства. Вы должны убедиться, что ваше определение и реализация соответствуют ожиданиям других программистов. Модульные тесты для типов, которые переопределяют Equals(), должны гарантировать, что реализация соблюдает эти правила:
- Рефлексивность (любой объект равен самому себе): независимо от типа, a = a всегда верно.
- Симметричность (порядок не имеет значения): если a = b, то b = a, если a <> b, то b <> a).
- Транзитивность: если a = b и b = c, то a = c.

Продолжение следует…

Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
День четыреста сорок пятый. #MoreEffectiveCSharp
8. Различные Концепции Равенства. Продолжение
Первая часть
3. Экземплярный метод Equals() переопределяют, когда поведение по умолчанию не соответствует семантике типа. Метод Object.Equals() по умолчанию ведёт себя точно так же, как Object.ReferenceEquals(), то есть проверяет ссылочное равенство. Но, например, System.ValueType (базовый класс для всех типов значений) переопределяет Object.Equals(): две переменные значимого типа равны, если они одного типа и имеют одинаковое содержимое. К сожалению, базовая реализация метода ValueType.Equals() не всегда эффективна. Если структура содержит ссылочный тип, для сравнения используется рефлексия:
struct StructNoRef {
public int X { get; set; }
public int Y { get; set; }
}
struct StructWithRef {
public int X { get; set; }
public int Y { get; set; }
public string Description { get; set; }
}

var stopwatch = new Stopwatch();
var data1 = new StructNoRef();
var data2 = new StructNoRef();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
data1.Equals(data2);
stopwatch.Stop();
WriteLine("StructNoRef: " + stopwatch.ElapsedMilliseconds);

stopwatch.Reset();
var data3 = new StructWithRef();
var data4 = new StructWithRef();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
data3.Equals(data4);
stopwatch.Stop();
WriteLine("StructWithRef: " + stopwatch.ElapsedMilliseconds);

Вывод:
StructNoRef: 66
StructWithRef: 1077

Проверка на равенство довольно часто вызывается в программах, поэтому её производительность не стоит игнорировать. Почти всегда вы можете написать намного более быстрое переопределение Equals() для любой структуры. Переопределим метод для StructWithRef:
struct StructWithRef {
/// …
public override bool Equals(object obj) {
if (!(obj is StructWithRef))
return false;
var other = (StructWithRef)obj;
return X == other.X &&
Y == other.Y &&
Description == other.Description;
}
}

Вывод:
StructNoRef: 61
StructWithRef: 81

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

Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
-
https://codewithshadman.com/csharp-data-types-and-object-tips/
День четыреста сорок шестой. #MoreEffectiveCSharp
8. Различные Концепции Равенства. Окончание
Первая часть, Вторая часть
Для ссылочных типов переопределённый метод Equals должен следовать предопределенному поведению, чтобы избежать странных сюрпризов для пользователей класса. Кроме того, нужно реализовать интерфейс IEquatable<T>. Вот стандартный шаблон:
public class Foo : IEquatable<Foo> {
public override bool Equals(object right) {
if (object.ReferenceEquals(right, null))
return false;
if (object.ReferenceEquals(this, right))
return true;
if (this.GetType() != right.GetType())
return false;

return this.Equals(right as Foo);
}
// IEquatable<Foo>
public bool Equals(Foo other) { … }
}
Метод не должен генерировать исключения, т.к. это не имеет особого смысла. Две ссылки либо равны, либо не равны, что может пойти не так? Просто верните false. Сначала проверяется, не является ли аргумент null (CLR не даст нам вызвать a.Equals(b), если a == null, будет выброшено NullReferenceException). Далее проверяется равенство ссылок. Обратите внимание, что для проверки принадлежности объектов к одному типу, вызывается GetType. Дело в том, что недостаточно проверить, можно ли привести right к типу Foo (например, if(right is Foo) {…}, как в примере из предыдущего поста со структурами). right может быть производным типом от Foo, значит его можно привести к базовому типу и проверка вернёт true. Однако базовый класс нельзя привести к производному. Таким образом нарушается правило симметричности равенства: a.Equals(b) и b.Equals(a) будут давать разные результаты. Далее метод делегирует проверку на равенство безопасному к типам методу Equals(Foo) - реализации интерфейса IEquatable<Foo>.

Примечание: переопределение метода Equals() также предполагает переопределение метода GetHashCode(). Об этом в будущих постах.

4. Оператор == переопределяется для значимых типов по тем же причинам, что и метод Equals() и обычно просто вызывает этот метод. Что касается ссылочных типов, в этом случае оператор == переопределяется редко, т.к. предполагается, что для всех ссылочных типов он реализует семантику проверки на ссылочное равенство.

Наконец, IStructuralEquatable реализуют System.Array и классы Tuple<>. Он позволяет им сравнивать объекты друг с другом, не заботясь о деталях семантики сравнения содержащихся в них элементов, потому что реализация метода Equals(Object, IEqualityComparer) принимает компаратор, который отвечает за это.

Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
День четыреста сорок седьмой. #DesignPatterns
Принципы SOLID.
1. Принцип единственной обязанности (SRP)
«У класса должна быть только одна причина для изменения»
(Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).

В разработке ПО есть одна неизменная составляющая — неизбежность изменений. Как бы мы ни старались всё продумать, требования изменяются: из-за изначального недопонимания задачи, изменений во внешнем мире или перехода на новую технологию. Но нам важны не причины изменений, а наши возможности по адаптации системы к новым требованиям и легкость внесения изменений.

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

Другой подход - использование наиболее простых решений: программная сущность (класс, модуль, метод) должна по возможности решать лишь одну задачу, но делать это хорошо. Таким образом, чем меньше у метода, класса или модуля вспомогательных задач, тем ниже вероятность случайных изменений.

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

Принцип SRP предназначен для борьбы со сложностью. В небольших приложениях проектирование практически не нужно. Проблемы возникают, когда система растёт. Добавление каждой новой функции требует всё больше усилий. В крупных системах важно иметь возможность сосредоточиться на главной задаче метода/класса/модуля и выбросить из рассмотрения все второстепенные детали.

Cложность принципа в том, что понятие «обязанности» является относительным. Может ли метод проверять свои аргументы? Или вести запись в лог? Наличие или отсутствие нарушения SRP очень зависит от того, насколько сложным является каждый из описанных шагов. Метод будет нарушать SRP, если за второстепенными функциями не видно основной логики. Но класс может и не нарушать SRP, если он читает и сохраняет данные, но на каждую операцию требуется две строки кода.

Примеры нарушения SRP
1. Смешивание логики с инфраструктурой. Бизнес-логика смешана с представлением, логикой репозитория и т.п.
2. Слабая связность. Класс/модуль/метод не является цельным, и разные его части обращаются к разным подмножествам данных.
3. Выполнение нескольких несвязанных задач. Класс/модуль/метод должен быть сфокусированным на решении минимального числа задач.
4. Решение задач разных уровней абстракции. Класс/метод не должен отвечать за задачи разного уровня (например, проверка аргументов, сериализация и шифрование). Каждый из этих аспектов должен решаться отдельным классом.

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

Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 17.
День четыреста сорок восьмой. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 9-13.
9. Что такое переопределение методов?
Переопределение означает изменение функциональности метода без изменения сигнатуры. Вы можете переопределить метод в базовом классе, создав аналогичный метод в производном классе. Это можно сделать с помощью ключевых слов virtual/override. Метод базового класса должен быть помечен ключевым словом virtual, и вы можете переопределить его в производном классе, используя ключевое слово override. Метод производного класса полностью переопределяет метод базового класса, т.е. если привести объект производного класса к базовому классу и вызвать метод, то будет вызвана реализация метода из производного класса.

10. Что означают ключевые слова Virtual, Override и New?
- virtual используется для изменения метода, свойства, индексатора или события, объявленного в базовом классе, и позволяет переопределять его в производном классе.
- override используется для расширения или изменения виртуального/абстрактного метода, свойства, индексатора или события базового класса в производном классе.
- new используется, чтобы скрыть метод, свойство, индексатор или событие базового класса в производном классе.

11. Что такое конструктор?
Конструктор - это специальный метод класса, который автоматически вызывается при создании экземпляра класса. Основное использование конструкторов - инициализация закрытых полей класса при создании экземпляра для класса.
Типы конструкторов:
- Параметризованный – принимает параметры и задаёт значения полей объекта.
- Конструктор по умолчанию – если в коде класса отсутствует конструктор, компилятор автоматически создаст конструктор по умолчанию, который инициализирует все числовые поля нулями, а все ссылочные поля в null.
- Конструктор копии – особый тип параметризованного конструктора, принимающий параметр того же типа, что и сам класс.
- Статический конструктор или конструктор типа - исполняется при первом обращении к типу
- Закрытый конструктор

12. Что такое закрытый конструктор?
Закрытый конструктор - это специальный конструктор экземпляра, который используется в классе, содержащем только статические члены. Если у класса есть один или несколько закрытых конструкторов и нет открытых конструкторов, то другим классам (кроме вложенных) не разрешается создавать экземпляры этого класса, это означает, что вы не можете ни создать объект класса. Закрытый конструктор используется для предотвращения создания объектов класса (чтобы можно было обращаться только к статическим членам), а также в паттерне Одиночка.

Советы по разработке конструкторов

13. Что такое деструктор?
Деструктор автоматически вызывается, когда объект уничтожается. Имя деструктора совпадает с именем класса и начинается с тильды (~). Деструктор используется для освобождения динамически распределенной памяти и освобождения ресурсов. Подробнее

Источник: https://www.c-sharpcorner.com
День четыреста сорок девятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
36. Миф о Гуру
Каждый, кто работал в ИТ достаточно долго, наверняка слышал подобный вопрос: «У меня возникла ошибка XYZ. В чем может быть проблема?»

При этом спрашивающие обычно не считают нужным приложить, трассировку стека, журналы ошибок или описать подробно контекст проблемы. Иногда кажется, что они считают, что вы живёте на другой планете, и решения приходят вам в голову без анализа ситуации. Они думают, что вы гуру.

Мы ожидаем таких вопросов от тех, кто не знаком с разработкой ПО - им работа системы может казаться почти волшебством. Но меня беспокоит, что я вижу это в сообществах разработчиков. Подобные вопросы возникают при разработке программы, например: «Я создаю управление складом. Надо ли использовать оптимистичную блокировку?» Самое смешное, что люди, задающие этот вопрос, зачастую лучше подготовлены к нахождению ответа, чем те, у кого они это спрашивают. Задающий вопрос обычно знает контекст, требования и может судить о преимуществах и недостатках различных стратегий. Тем не менее, они ожидают, что вы дадите разумный ответ в отсутствии контекста. Они ожидают волшебства.

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

Конечно, может быть огромная разница в естественных способностях разных людей. Многие хакеры умнее, образованнее и работают продуктивнее, чем я когда-либо буду. Тем не менее, развенчание мифа о гуру окажет положительное влияние. Например, если я работаю с кем-то умнее меня, я стараюсь обеспечить для него достаточно контекста, чтобы человек мог эффективно применить свои навыки. Развенчание мифа о гуру также приведет к устранению мнимого барьера для саморазвития. Вместо магического барьера я вижу путь, по которому я могу двигаться вперёд.

Наконец, одно из самых больших препятствий для развития ПО - умные люди, которые целенаправленно распространяют миф о гуру. Это может быть из-за их эго или как стратегия повышения своей ценности в глазах клиента или работодателя. По иронии судьбы такое отношение может сделать умных людей менее ценными, поскольку они не способствуют росту своих коллег. Нам не нужны гуру. Нам нужны специалисты, желающие развивать других специалистов в своей области. Найдётся место для всех.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Ryan Brush
День четыреста пятидесятый. #юмор
Ну и в тему, тут Дудь выкатил гигантский видос про чуть более успешные стартапы)))
День четыреста пятьдесят первый. #MoreEffectiveCSharp
9. Тонкости GetHashCode(). Начало
GetHashCode() – одна из тех функций, которую, по возможности, не стоит реализовывать самостоятельно. Она используется только в одном месте: для определения значения хеш-функции для ключей в коллекциях на основе хеша (обычно это HashSet<T> или Dictionary<K,V>). Есть ряд проблем с реализацией GetHashCode() в базовом классе. Для ссылочных типов она работает, но не очень эффективно. Для типов значений есть специальные оптимизации, если он не имеет ссылочных полей, в противном случае функция работает медленно и не всегда верно. Но дело не только в этом. Скорее всего, вы не сможете самостоятельно реализовать GetHashCode() и эффективно, и правильно.

Контейнеры используют хэш-коды для оптимизации поиска по коллекции, так что он занимает время близкое к постоянному, независимо от размера. Если вы определяете тип, который никогда не будет использоваться в качестве ключа в контейнере, ничего делать не нужно: ссылочные типы будут иметь правильный хеш-код, значимые типы должны быть неизменяемыми, и в этом случае реализация по умолчанию всегда будет работать. В большинстве типов, которые вы создаёте, лучшим подходом будет игнорирование существования GetHashCode().

В .NET каждый объект имеет хеш-код, определённый в System.Object.GetHashCode(). Любая перегрузка GetHashCode() должна следовать трём правилам:
1. Если два объекта равны (как определено экземплярным методом Equals()), они должны генерировать один и тот же хеш-код.
Версия оператора == в System.Object проверяет идентичность объектов. GetHashCode() возвращает внутреннее поле - идентификатор объекта, и это правило работает. Однако, если вы переопределили метод Equals(), нужно переопределить и GetHashCode(), чтобы обеспечить соблюдение правила.

2. Для любого объекта A хеш-код должен быть инвариантен. То есть, независимо от того, какие методы вызываются на объекте, A.GetHashCode() всегда должен возвращать одно и то же значение. К примеру, изменение значения поля не должно приводить к изменению хэш-кода.

3. Хеш-функция должна генерировать равномерное распределение среди всех целых чисел для всех типичных входных наборов. Это более-менее соблюдается для System.Object.

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

Для типов-значений стандартная версия GetHashCode возвращает хэш-код только первого ненулевого поля, смешивая его с идентификатором типа. Проблема в том, что в этом случае начинает играть роль порядок полей типа. Если значение первого поля экземпляров одинаково, то хэш-функция будет выдавать одинаковый результат. Таким образом, если большинство экземпляров типа имеют одинаковое значение первого поля, то производительность поиска по хэш-набору или хэш-таблице из таких элементов резко упадет (см. тест в статье на Хабре).

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

Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 10.
-
https://habr.com/ru/company/microsoft/blog/418515/
День четыреста пятьдесят второй. #MoreEffectiveCSharp
9. Тонкости GetHashCode(). Окончание
Для переопределения GetHashCode нужно наложить некоторые ограничения на тип. В идеале он должен быть неизменяемым. Посмотрим ещё раз на правила:
1. Если два объекта равны по методу Equals(), они должны возвращать одинаковый хэш. Следовательно, любые данные, используемые для генерации хеш-кода, также должны участвовать в проверке на равенство.

2. Возвращаемое значение GetHashCode() не должно изменяться. Допустим, у нас есть класс, в котором мы переопределили GetHashCode:
public class Customer {
public Customer(string name) => Name = name;
public string Name { get; set; }
// другие свойства
public override int GetHashCode()
=> Name.GetHashCode();
}

Мы разместили объект Customer в HashSet, a затем решили изменить значение Name:
var cs = new HashSet<Customer>();
var c = new Customer("Ivanov");

cs.Add(c);
Console.WriteLine(
$"{cs.Contains(c)}, всего: {cs.Count}");
c.Name = "Petrov";
Console.WriteLine(
$"{cs.Contains(c)}, всего: {cs.Count}");
Вывод:
True, всего: 1
False, всего: 1

Хэш-код объекта изменился. Объект по-прежнему находится в коллекции, но теперь его невозможно найти. Единственный способ удовлетворить правилу 2 - определить хеш-функцию, которая будет возвращать значение на основе некоторого инвариантного свойства или свойств объекта. System.Object соблюдает это правило, используя идентификатор объекта, который не изменяется.

3. GetHashCode() должен генерировать случайное распределение среди всех целых чисел для всех входных данных. Здесь не существует волшебной формулы. Если тип содержит некоторые изменяемые поля, исключите их из вычислений.

Microsoft предоставляет хороший универсальный генератор хэш-кодов. Просто скопируйте значения свойств/полей (соблюдая правила выше) в анонимный тип и хешируйте его:
public override int GetHashCode() 
=> new { PropA, PropB, PropC, … }.GetHashCode();
Это будет работать для любого количества свойств. Здесь не происходит боксинга, и используется алгоритм, уже реализованный для анонимных типов.

Для C# 7+ можно использовать кортеж. Это сэкономит несколько нажатий клавиш и, что более важно, выполняется исключительно на стеке (без мусора):
public override int GetHashCode()
=> (PropA, PropB, PropC, …).GetHashCode();

Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 10.
-
https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-overriding-gethashcode
День четыреста пятьдесят третий. #DesignPatterns
Принципы SOLID.
2. Принцип «открыт/закрыт» (OCP)
«Программные сущности (классы, модули, функции и т. п.) должны быть открытыми для расширения, но закрытыми для модификации» (Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).

Одна из причин «загнивания» дизайна кроется в страхе внесения изменений. Разработчики и менеджеры должны быть уверены в том, что изменения являются корректными и не приведут к появлению ошибок в других частях системы. Простые классы и модули, которые соответствуют принципу единственной обязанности, являются хорошей стартовой точкой, но этого не всегда достаточно.
По мере развития в системе появляются семейства типов с общим поведением и схожими интерфейсами. Возникают иерархии наследования, в базовых классах которых помещается общее поведение, которое наследники изменяют при необходимости. Это позволяет повторно использовать значительную часть логики, а также упрощает добавление типов с новым поведением. Полученные иерархии типов одновременно являются открытыми (просто добавлять новые типы) и закрытыми (интерфейсы базовых классов стабильны).

Смысл принципа OCP: дизайн системы должен быть простым и устойчивым к изменениям.
Т.е. когда требования изменятся, вы должны быть к этому готовы. Не нужно создавать дополнительные уровни абстракции без необходимости, но нужно ограничить каскад изменений и свести их количество к минимуму. Как этого добиться?

1. За счет абстракции и инкапсуляции. Выделение важных частей системы в открытой части класса позволяет сосредоточиться на важных аспектах поведения, не задумываясь о реализации, скрытой от клиентов в закрытой его части. При этом абстракция не требует наличия интерфейсов или абстрактных классов.
Сокрытие информации заключается не только в наличии закрытых полей, недоступных клиентам, но и подразумевает сокрытие реализации, т.е. позволяет думать о классе как о чёрном ящике, который предоставляет определённые в интерфейсе услуги лишь ему известным способом.
Таким образом в классе разделяют интерфейс и реализацию: в интерфейсе собрано всё, что касается взаимодействия другими объектами; реализация скрывает от других объектов детали, не имеющие отношения к процессу взаимодействия.
2. За счет наследования. Выделение интерфейсов и абстрактных классов позволяет думать о задаче ещё более абстрактно. Полиморфное поведение позволяет заменить один вариант реализации на другой во время исполнения, а также повторно использовать код.

Принцип единственного выбора
Всякий раз, когда система должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы. Фабрика отвечает принципу «открыт/закрыт», если список вариантов является её деталью реализации. Если же информация о конкретных типах иерархии начинает распространяться по коду приложения и в нем появляются проверки типов (as или is), то это решение уже перестанет следовать принципу «открыт/закрыт». Тогда добавление нового типа потребует каскадных изменений в других модулях, что негативно отразится на стоимости изменения.

Примеры нарушения OCP
1. Интерфейс класса является нестабильным. Постоянные изменения интерфейса класса, используемого во множестве мест, приводят к постоянным изменениям во многих частях системы.
2. «Размазывание» информации об иерархии типов. В коде постоянно используются понижающие приведения типов (downcasting), что «размазывает» информацию об иерархии типов по коду приложения. Это затрудняет добавление новых типов и усложняет понимание текущего решения.

Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 18.
День четыреста пятьдесят четвёртый. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 14-19.
14. Что такое интерфейс?
Интерфейс – это программная структура, определяющая отношение между объектами, которые разделяют определённое поведение и не связаны никак иначе. Интерфейсы не содержат реализации, а только объявляют события, индексаторы, методы и свойства. Структуры и классы, реализующие интерфейс, должны обеспечивать реализацию для каждого объявленного члена интерфейса.

15. Зачем использовать интерфейсы в C#?
Интерфейсы в основном используются в C# для расширяемости, сокрытия реализации, доступа к разнородным объектам через интерфейс и создания слабо связанных систем.

16. Что такое неявная и явная реализация интерфейса?
Если в C# перед именем метода вы ставите имя интерфейса, в котором определён этот метод, то вы создаёте явную реализацию интерфейсного метода. Явная реализация метода, в отличие от обычной (неявной) недоступна через экземпляры класса. Чтобы получить доступ к явной реализации, объект нужно привести к интерфейсному типу.
Цели:
1) Исключить методы из общедоступного интерфейса класса.
2) Избежать путаницы между двумя реализациями методов с одинаковой сигнатурой. Подробнее.
См. также: Пример явной реализации интерфейса.

17. Что такое абстрактный класс?
Абстрактный класс - это особый класс, экземпляр которого не может быть создан. Абстрактный класс предназначен для наследования от него. Основным преимуществом является то, что он обеспечивает общее поведение или контракт для всех подклассов. Абстрактный класс может иметь как абстрактные, так и не абстрактные методы. Абстрактные члены могут быть определены только в абстрактном классе, и должны быть переопределены в потомках.

18. Почему нельзя создавать экземпляры абстрактного класса?
Потому что абстрактный класс не содержит полной реализации, его абстрактные методы не могут быть вызваны. Если компилятор позволял бы создать экземпляр абстрактного класса, то можно было бы вызвать абстрактный метод, что приводило бы к ошибке времени выполнения.

19. Когда использовать абстрактный класс?
Когда в базовом классе нужно предоставить реализацию определённых методов по умолчанию, тогда как другие методы должны быть открыты для переопределения дочерними классами. Абстрактный класс применяется, например, в паттерне «Шаблонный метод».

См. также: Чем отличается интерфейс от абстрактного класса в C#?

Источник: https://www.c-sharpcorner.com
День четыреста пятьдесят пятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
37. Тяжёлая Работа не Окупается
Как программист, вы обнаружите, что упорный труд часто не окупается. Вы можете обманывать себя и некоторых коллег, полагая, что вносите большой вклад в проект, проводя долгие часы в офисе. Но правда в том, что, работая меньше, вы можете достичь большего, иногда гораздо большего. Если вы пытаетесь быть сосредоточенным и «продуктивным» более 30 часов в неделю, вы, вероятно, слишком много работаете. Вам следует подумать о снижении рабочей нагрузки, чтобы стать более эффективным и сделать больше.

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

Профессиональное программирование обычно не похоже на марафон, где цель видна в конце асфальтированной дороги. Большинство программных проектов больше смахивают на спортивное ориентирование. В темноте. С наспех нарисованным планом вместо карты. Если вы просто выбрали направление и бежите туда изо всех сил, вы можете произвести на кого-то впечатление, но вряд ли прибежите к финишу. Вы должны поддерживать разумный темп и корректировать курс, когда узнаёте больше о том, где вы находитесь и куда направляетесь.

Кроме того, всегда нужно больше узнавать о разработке ПО в целом и о методах программирования в частности. Читать книги, ходить на конференции, общаться с другими профессионалами, экспериментировать с новыми методами реализации и узнавать о мощных инструментах, которые упрощают вашу работу. Как профессиональному программисту, вам нужно постоянно поддерживать необходимый уровень квалификации в своем деле, точно также, как если бы вы были нейрохирургом или пилотом самолёта. Вы должны проводить вечера, выходные и праздничные дни, занимаясь самообразованием, поэтому вы не можете тратить это время, работая сверхурочно над текущим проектом. Вы действительно ожидаете, что нейрохирурги будут выполнять операции по 60 часов в неделю, или пилоты летать по 60 часов в неделю? Конечно, нет: подготовка и образование являются неотъемлемой частью их профессии.

Сосредоточьтесь на проекте, вносите максимальный вклад, находя умные решения, улучшайте свои навыки, размышляйте над тем, что вы делаете, и адаптируйте своё поведение. Не дискредитируйте себя и профессию, работая с упорством и эффективностью белки в колесе. Как профессиональный программист, вы должны знать, что пытаться быть сосредоточенным и «продуктивным» 60 часов в неделю - неразумная вещь. Действуйте как профессионал: подготовка, действие, наблюдение, анализ, изменение.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Olve Maudal
День четыреста пятьдесят шестой. #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/