.NET Разработчик
6.5K 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
День 1937. #ЗаметкиНаПолях
Введение в Первичные Конструкторы в C#12. Начало

Первичные конструкторы были впервые представлены в C#9 как часть функциональности записей. В C#12 они стали доступны обычным типам классов и структур.

Чтобы создать тип Person до C#12 нужно было бы написать примерно такой класс:
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}

Здесь много дублирования. Первичные конструкторы позволяют упростить определение и уменьшить дублирование.

Способы использования параметров первичного конструктора:
1. Инициализировать члены (или передать их базовому конструктору).
2. Использовать прямую ссылку на параметры.

1. Инициализация членов параметрами
Инициализация членов напрямую аналогична определению класса, показанному выше, но уменьшает часть дублирования и, как правило, более компактна:
public class Person(string firstName, string lastName)
{
public string FirstName { get; } = firstName;
public string LastName { get; } = lastName;
}

Мы почти вдвое сократили количество кода, при этом сохранив его намерение понятным и очевидным. Заметьте, здесь мы используем классы, но для структур всё будет так же. Аналогично можно задать и приватные поля:
public class Person(string firstName, string lastName)
{
private readonly string _firstName = firstName;
private readonly string _lastName = lastName;

public string FullName => $"{_firstName} {_lastName}";
}

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

2. Ссылаемся на параметры конструктора в членах класса
Если значения приватных полей не используются нигде, кроме свойства FullName, мы можем использовать первичный конструктор, чтобы ещё больше «упростить» класс:
public class Person(string firstName, string lastName)
{
// Напрямую ссылаемся на
// параметры первичного конструктора
public string FullName => $"{firstName} {lastName}";
}

Но мы можем пойти и дальше, и изменять параметры первичного конструктора:
public class Person(string firstName, string lastName)
{

public void SetName(string first, string last)
{
firstName = first;
lastName = last;
}
}

Если вы выполните следующий код, вы увидите, что параметры конструктора изменяемые:
var p = new Person("Andrew", "Lock");
p.SetName("Jon", "Smith");
Console.WriteLine(p.FullName); // "Jon Smith"

Далее рассмотрим, как это работает «под капотом».

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

Источник:
https://andrewlock.net/an-introduction-to-primary-constructors-in-csharp-12/
👍13
День 1938. #ЗаметкиНаПолях
Введение в Первичные Конструкторы в C#12. Окончание

Начало

Отличный способ понять многие новые функции C#, особенно те, которые по сути являются синтаксическим сахаром, — использовать Sharplab.io, чтобы увидеть, как компилятор «понижает» функции до более простого C#.

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

А вот если мы используем параметры первичного конструктора в членах класса, код будет очень похож, но всё же отличаться.

Если вы ссылаетесь на параметры основного конструктора где-либо, кроме инициализатора поля или свойства, компилятор автоматически сгенерирует поля с невалидными в обычном C# именами, типа
private string <firstName>P;

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

Добавление проверки к инициализации
Одной из общих проблем, связанных с такими функциями, как первичные конструкторы, является проблема «роста». Есть риск, что функция отлично работает в простых случаях, но как только вам нужно что-то более сложное, приходится полностью от неё отказаться.

Например, в конструкторы обычно добавляют код проверки на null или недопустимость аргументов. В первичных конструкторах их по-прежнему можно использовать:
public class Person(string firstName, string lastName)
{
private readonly string _firstName =
firstName
?? throw new ArgumentNullException(nameof(firstName));
private readonly string _lastName =
IsValidName(lastName)
? lastName
: throw new ArgumentNullException(nameof(lastName));

public string FullName => $"{_firstName} {_lastName}";

private static bool IsValidName(string name)
=> name is not "";
}

Вот результат компиляции.

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

Мы рассмотрели большинство важных возможностей первичных конструкторов, но не затронули одного вопроса: надо ли их использовать? Об этом в будущих постах.

Источник: https://andrewlock.net/an-introduction-to-primary-constructors-in-csharp-12/
👍12
День 1939. #ЗаметкиНаПолях
Функции C# и Целевая Платформа

Если вы заглянете на официальную страницу версий языка C#, то можно подумать, что существует сильная связь между целевой платформой и версией языка. И действительно, если вы не укажете версию языка явно в файле проекта, она будет выбрана на основе целевой платформы: C# 12 для .net8, C# 11 для .net7 и C# 7.3 для .NET Framework.

Фактическая же связь между версией языка и целевой платформой более тонкая. Есть три способа, как функция языка может быть связана с целевой платформой:
1. Просто работает
Некоторые функции работают сразу после установки версии языка. Установите нужную версию языка в блоке компиляции файла проекта
<LangVersion>12.0</LangVersion>

и новая функция будет работать независимо от целевой платформы.

2. Требуется специальное определение типа
Требует, чтобы компилятор обнаруживал специальные типы. Специальные типы добавляются в основной выпуск .net, соответствующий конкретной версии C#, но вы можете добавить их в свою компиляцию вручную, чтобы эти функции заработали. Компилятору не важно, откуда берётся определение типа: из целевой платформы, из NuGet-пакета или просто является частью проекта.
Например, следующая запись с init-сеттером и обязательным свойством:
public record class MyRecord
{
public int X { get; init; }
public required int Y { get; set; }
}

доступна в проекте netstandard2.0 или net472 при добавлении в проект следующего кода:
namespace System.Runtime.CompilerServices
{
// для init-сеттера
internal class IsExternalInit { }
// для обязательного свойства
internal class RequiredMemberAttribute : System.Attribute { }
internal sealed class CompilerFeatureRequiredAttribute(string featureName) : System.Attribute
{
public string FeatureName { get; set; } = featureName;
}
}
// для обязательного свойства
namespace System.Diagnostics.CodeAnalysis
{
internal class SetsRequiredMembersAttribute : System.Attribute { }
}

Чаще всего компилятор сообщит вам, какого типа или члена типа ему не хватает для успешной компиляции.

3. Зависит от времени выполнения
Лишь небольшая часть новых возможностей языка требует поддержки среды исполнения. Это, например, реализации интерфейсов по умолчанию, встроенные массивы или ref-поля. Такие функции не будут компилироваться, если целевая платформа их не поддерживает. В этом случае компилятор сообщит, что целевая среда выполнения не поддерживает эту функцию.

Трюк с InternalsVisisbleTo
Существует проблема с показанным ранее решением для 2го варианта. Допустим, у вас есть MyProject.csproj, ориентированный на netstandard2.0, и MyProject.Tests.csproj, ориентированный на net8.0 с InternalVisibleTo("MyProject.Tests") внутри MyProject.csproj для доступа к приватным классам проекта из тестов.

В этом случае вы не сможете скомпилировать MyProject.Tests.csproj с ошибкой о повторяющемся определении члена, поскольку тип IsExternalInit будет доступен из двух мест — из MyProject.csproj и из библиотеки времени выполнения .net8.0.

Решение довольно простое: используйте несколько целевых сред выполнения в проекте MyProject.csproj, например:
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>


Источник: https://sergeyteplyakov.github.io/Blog/c%23/2024/03/06/CSharp_Language_Features_vs_Target_Frameworks.html
👍14
День 1940. #МоиИнструменты
Генерируем http-файлы из Спецификации OpenAPI

Http-файлы хороши и удобны, но их обновление также требует определенных усилий. Почему бы не сгенерировать их из cпецификации OpenAPI?

Когда вы создаете новый http-файл для веб-API в .NET 8, вы получаете файл MyProjectName.http. Он выглядит примерно так:
@MyWebApp_HostAddress = https://localhost:5059

GET {{MyWebApp_HostAddress}}/weatherforecast/
Accept: application/json

###

Этот файл используется расширением REST Client в Visual Studio Code, Visual Studio и Rider, что позволяет отправлять HTTP-запросы к вашему API прямо из редактора.

Но есть проблема: их обновление может быть хлопотным. Представьте, что мы добавляем в наш API две новые конечные точки:
app.MapPost("/weatherforecast", 
(CreateWeatherDto dto) => TypedResults.Ok())
.WithName("CreateWeatherForecast")
.WithOpenApi();
app.MapDelete("/weatherforecast/{id}",
(int id) => TypedResults.Ok())
.WithName("DeleteWeatherForecast")
.WithOpenApi();

Чтобы сгенерировать для них запросы в http-файл, используем утилиту httpgenerator.

Установка её очень проста:
dotnet tool install --global httpgenerator 

Теперь её можно использовать, передав ей путь к спецификации OpenAPI (локальный или, при запущенном API, URL):
httpgenerator https://localhost:5059/api/v1/openapi.json --output-type OneFile

--output-type OneFile объединит все конечные точки в один файл, иначе вы получите n файлов для каждой конечной точки, что не очень удобно. Файл всегда будет называться Requests.http и будет помещён в текущий каталог (если не указано иное с помощью параметра --output). Поэтому вы можете переименовать его в MyProjectName.http, чтобы соответствовать соглашению об именах. Получим примерно такой результат:
@contentType = application/json

###################################
### Request: GET /weatherforecast
###################################

GET https://localhost:5059/weatherforecast
Content-Type: {{contentType}}

####################################
### Request: POST /weatherforecast
####################################

POST https://localhost:5059/weatherforecast
Content-Type: {{contentType}}

{
"temperatureC": 0,
"summary": "summary"
}

###########################################
### Request: DELETE /weatherforecast/{id}
###########################################

### Path Parameter: DeleteWeatherForecast_id
@DeleteWeatherForecast_id = 0


DELETE https://localhost:5059/weatherforecast/{{DeleteWeatherForecast_id}}
Content-Type: {{contentType}}

Утилита также поддерживает другие параметры, в том числе передачу Bearer-токенов, о чём можно почитать на странице проекта в GitHub.

Источник: https://steven-giesel.com/blogPost/9fa236ef-67da-4113-95e7-99770dc70444/generate-http-files-from-a-swagger-definition
👍20
Какая из перегрузок метода AddNumbers будет выбрана? (см. картинку в первом комментарии)
#Quiz #CSharp
Anonymous Quiz
45%
int[]
31%
IEnumerable<int>
24%
ReadOnlySpan<int>
👍12👎1
День 1941. #Книги
«Как пасти котов. Наставление для программистов, руководящих другими программистами» (Рейнвотер Дж. — СПб.: Питер, 2022).

Автор рассказывает о том, как преуспеть в роли руководителя команды программистов, если вы сами программист, а не менеджер. Какие привычки вам придётся изменить? Как организовать свою работу и работу команды? Как управлять теми, кто у вас в подчинении? А с котами программистов сравнивают не зря, мы такие же своенравные, свободолюбивые и не поддающиеся дрессировке. Как общаться с вашим начальством и с другими отделами? И о многом другом.

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

Если вы нацелились на роль тимлида, обязательно прочитайте, что вас ждёт и как с этим справиться.
👍25👎2
День 1942. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 10. Требования должны быть достаточно хорошими, чтобы разработка могла продолжаться с приемлемым уровнем риска

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

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

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

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

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

2. Широта знаний
Объём информации, содержащейся в спецификации. В неё входят все требования пользователей или только высокоприоритетные? Учитываются все атрибуты качества или только первостепенные? Очевидно ли читателю требований, что перед ним полные или не полные требования? Если набор требований неполный, то все ли читатели увидят одни и те же пробелы? Если подразумеваемые и предполагаемые требования нигде не фиксируются, то высока вероятность, что они останутся незамеченными.

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

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

Многие команды Agile-разработки ПО не оформляют все детали требований в письменном виде, но это не значит, что эти детали не нужны разработчикам и тестировщикам. Если письменная информация недоступна во время реализации, то должна быть возможность получить её из нужного источника. В противном случае члены команды должны будут сами восполнять пробелы, что может быть отмечено заказчиком как недостаток. А это будет означать, что требования не полностью готовы для реализации.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍8
День 1943. #Оффтоп
Быть Разработчиком ПО «На Вызове» - это Нормально?
Сегодня (в выходной день для обычных людей) рассмотрим вопрос с одного из форумов Stackexchange по поводу работы дежурными «на вызове».

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

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

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

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

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

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

Источник: https://workplace.stackexchange.com/questions/185868/are-on-call-responsibilities-for-software-developers-normal-or-unusual
👍7
День 1944. #ЗаметкиНаПолях
Мысли о Первичных Конструкторах. Начало

См. также Введение в Первичные Конструкторы в C#12.

Лучшие варианты использования
Рассмотрим случаи, для которых первичные конструкторы хорошо подходят.

1. Базовая инициализация поля
Первичные конструкторы сокращают объём кода, который вам нужно написать, и вместо этого компилятор генерирует этот код за вас:
public class Person(string firstName, string lastName)
{
private readonly string _firstName = firstName;
private readonly string _lastName = lastName;
}

Однако, даже добавление валидации уже делает код не таким очевидным:
public class Person(string firstName, string lastName)
{
private readonly string _firstName =
!string.IsNullOrEmpty(firstName)
? firstName
: throw new ArgumentException(
"Must not be null or empty", nameof(firstName));

}

Вы не можете использовать защитные методы, доступные в .NET 7+:
ArgumentException.ThrowIfNullOrEmpty(firstName);

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

2. Инициализация в тестовом коде
Возьмем простой пример теста xunit для типа Person:
public class PersonTests(ITestOutputHelper output)
{
[Theory]
[InlineData("Jon", "")]
public void Person_throws_if_null_or_empty(
string firstName, string lastName)
{
output.WriteLine(
$"Testing '{firstName}' and '{lastName}'");
Assert.Throws<ArgumentException>(() =>
new Person(firstName, lastName));
}
}

Опустим тут ценность этого теста, и нужно ли нам использовать ITestOutputHelper в методе. Здесь интересно то, что добавление ITestOutputHelper в качестве параметра первичного конструктора упрощает тестовый класс. Для теста не имеет значения, хранится ли ITestOutputHelper в поле или нет, и является ли он изменяемым.

3. Внедрение зависимостей в контроллерах MVC
В ASP.NET Core, вы, вероятно, используете внедрение зависимостей и не часто проверяете зависимости, которые внедряются в контроллеры. Это делает их хорошими кандидатами для первичных конструкторов (можно даже использовать значения напрямую):
public class MyController(
ILogger<MyController> logger,
IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Thing> Get()
{
logger.LogInformation("Get the thing");
return service.GetThing();
}
}

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

Фактически, вы, вероятно, обнаружите, что большая часть ежедневного кода, который вы пишете в своём приложении, может использовать первичные конструкторы без какого-либо существенного влияния, кроме сокращения строк кода.

Тем не менее, при использовании первичных конструкторов есть вещи, на которые стоит обратить внимание.

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

Источник:
https://andrewlock.net/thoughts-about-primary-constructors-3-pros-and-5-cons/
👍12
День 1945. #ЗаметкиНаПолях
Мысли о Первичных Конструкторах. Окончание

Начало

Подводные камни
Замечание: описанные ниже «проблемы» не означают, что нельзя использовать первичные конструкторы, и для вас они даже могут показаться несущественными.

1. Повторный захват
Если вы инициализируете поле с помощью параметра первичного конструктора, а также используете его значение в другом члене класса, у вас будет два поля, хранящих одно и то же значение:
public class Person(string name)
{
private string _name = name;

public string Greeting
=> $"Hello, {name}";
}

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

2. Неявные поля не могут быть readonly
Если вы неявно захватываете параметр первичного конструктора, компилятор генерирует поле для его хранения (см. подробнее). Важно, что они изменяемы, т.е. параметру (и этому полю) можно задавать значения в теле класса. Если это то, что вам нужно, нет проблем. Но если поле не должно быть изменяемо, обычно стоит отметить его как readonly, что упрощает понимание назначения поля и устраняет целый класс ошибок, связанных со случайным изменением поля. Не удивлюсь, если это исправят в будущих версиях C#, но сейчас это немного раздражает. Решение в том, чтобы не использовать неявный захват, если вам нужны поля только для чтения.

3. Путаница в соглашениях об именах
Не большая проблема, но это придётся обсудить в команде на раннем этапе: какие соглашения следует использовать для параметров первичного конструктора? Использовать ли для параметров префикс _? Если вы используете неявный захват в коде класса, то там они будут по сути как приватные поля:
public class Person(string _firstName, string _lastName)
{
public string FullName => $"{_firstName} {_lastName}";
}

С другой стороны, создание экземпляра такого класса будет выглядеть… странно:
var p = new Person(_firstName: "Jon", _lastName: "Smith");

Microsoft рекомендует именование как параметров (без префикса). Кстати, вы не можете использовать this. в коде для обращения к параметрам первичного конструктора, такой код просто не скомпилируется.

4. Неявные поля меняют размер структур
В высокопроизводительном коде или при работе с interop иногда бывает важен размер экземпляров и как выстроено их содержимое. Если у нас есть следующая структура:
public struct Person(
string first,
string last,
int age)
{
public int Age = age;
public string FullName => $"{first} {last}";
}

То мы имеем 3 поля (1 – int и 2 – ссылки на string) для неявных полей, всего 8*3=24 байта на экземпляр. Теперь, если мы изменим первичный конструктор:
public struct Person(
string name,
int age)
{
public int Age = age;
public string FullName => name;
}

Внезапно вместо 3 полей останется 2 и размер станет 16 байт, но это без явного изменения публичных полей структуры (если вы не знаете, как реализованы первичные конструкторы). Это может иметь очень плохие последствия, если мы полагаемся на размер Person где-то в коде!

5. Путаница с записями
Всё-таки стоит помнить, что, несмотря на почти идентичный синтаксис, следующие объявления не взаимозаменяемы:
public record Person(string FirstName, string LastName);

public class Person(string FirstName, string LastName);

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

Источник: https://andrewlock.net/thoughts-about-primary-constructors-3-pros-and-5-cons/
👍16👎1
День 1946. #ЧтоНовенького
Гибридный Кэш в .NET 9

В .NET 9 Превью 4 появился новый гибридный вид кэширования.

Кэш в памяти
IMemoryCache - позволяет кэшировать объекты в памяти. Это простое хранилище пар ключ-значение:
builder.Services.AddMemoryCache();

public async BlogPost GetPost(
int id, CancellationToken ct = default)
{
// Cache key
var key = $"BlogPost_{id}";
var post = await _memoryCache
.GetOrCreateAsync(key, async entry =>
{
return await _blogRepo.GetPostAsync(id, ct);
});

return post;
}

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

Распределённый кэш
IDistributedCache - обычно используется для связи между несколькими сервисами и/или если вам необходимо сохранить данные в течение всего срока службы вашего приложения (то есть после завершения работы и перезапуска сервера). Известный пример: Redis.
builder.Services
.AddStackExchangeRedisCache();

Распределённый кэш не имеет такого удобного метода, как GetOrCreateAsync, поэтому вам придётся вручную пытаться извлечь значение из кэша с помощью GetAsync, а если он вернёт null, то получить значение и сохранить его в кэш.

«Паника в кэше» (cache stampede) — это тип каскадного сбоя, который может возникнуть при высокой нагрузке. Сбой возникает, если на получение значения требуется больше времени, чем время между запросами этого значения. Тогда все эти запросы не будут использовать кэш, а вызовут код получения значения. В этом случае ожидание заполнения кэша может быть лучшей стратегией, но этот код нужно писать вручную.

Гибридный кэш
HybridCache (абстрактный класс, а не интерфейс!) - представлен в превью .NET 9, но доступен (благодаря netstandard2.0) даже для .NET Framework 4.7.2 и призван заменить оба предыдущих вида:
builder.Services.AddHybridCache();

public async BlogPost GetPost(
int id, CancellationToken ct = default)
{
return await cache.GetOrCreateAsync(
$"BlogPost_{id}",
async cancel =>
await _blogRepo.GetPostAsync(id, cancel),
token: ct
);
}

Это очень похоже на API IMemoryCache, но «под капотом» ведёт себя как IDistrubtedCache, при этом решая проблемы паники в кэше. По умолчанию используется кэш в памяти, но при добавлении распределённого кэша в сервисы, он будет подключен к гибридному кэшу автоматически. Кроме того, он добавляет несколько новых функций, таких как создание тэгов и возможность удалить все значения в кэше по тегу или флаги, например, чтобы отключить сохранение данных в распределённом кэше.

Источник: https://steven-giesel.com/blogPost/e3ee537a-f70b-43d3-8d13-5cf81bc4406b/memorycache-distributedcache-and-hybridcache
👍35
День 1947. #ЗаметкиНаПолях #BestPractices
Правильное Логирование Минимальных API в Serilog. Начало
В этой серии рассмотрим, как с максимальной пользой использовать Serilog в приложениях минимальных API ASP.NET Core 8. Серия будет состоять из 3х частей.

1. Настройка
Приложения ASP.NET Core — «всего лишь» консольные приложения. Вы можете настроить Serilog и использовать его вообще без каких-либо особенностей ASP.NET Core. Для «пустого» проекта «минимального API» файл Program.cs будет выглядеть так:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Установим Serilog и консольный приёмник (sink):
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console

В начале Program.cs создадим пайплайн логирования и назначим его статическому свойству Log.Logger:
using Serilog;

Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();

Log.Information("Starting up");

// … остальной код минимального API

Теперь при запуске должно появиться сообщение «Starting up» от Serilog, а затем вывод по умолчанию от ASP.NET.

Если приложение не запускается, мы хотим перехватить все возникающие исключения, а также убедиться, что все события буферизованного журнала записаны перед завершением процесса. Добавим try/catch/finally:
using Serilog;

// … пайплайн логирования

try
{
// … код минимального API
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Unhandled exception");
return 1;
}
finally
{
await Log.CloseAndFlushAsync();
}

Здесь мы можем использовать класс Log для записи собственных структурированных событий журнала.

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

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

Источник:
https://nblumhardt.com/2024/04/serilog-net8-0-minimal/
👍5
День 1948. #ЗаметкиНаПолях #BestPractices
Правильное Логирование Минимальных API в Serilog. Продолжение

1. Настройка

2. Связываем ASP.NET Core и ILogger<T>
Это очень просто. Установим Serilog.Extensions.Hosting:
dotnet add package Serilog.Extensions.Hosting

И вызовем AddSerilog():

try
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSerilog();

Теперь логи приложения будут писаться в Serilog.

Если вы хотите записывать события в логи, нужно настроить место назначения: путь к файлу или URL сервера логов.

Для примера используем Seq:
dotnet add package Serilog.Sinks.Seq

Добавим его в пайплайн.
Вариант 1 – в коде, используя переменные среды:
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.Seq(
Environment.GetEnvironmentVariable("SEQ_URL")
?? "https://localhost:5341",
apiKey:
Environment.GetEnvironmentVariable("SEQ_API_KEY"))
.CreateLogger();

Чтобы установить контейнер Seq, работающий по адресу https://localhost:5341, выполните:
docker run --rm -it -e ACCEPT_EULA=y -p 5341:80 datalust/seq

Теперь после запуска приложения, можете перейти по адресу https://localhost:5341 в браузере и увидеть логи приложения.
Этот вариант удобен, т.к. компилятор проверит, предоставлены ли все необходимые параметры, и всё, что не изменяется во время развёртывания, может быть указано в строго типизированном C#. Он также надёжно работает при публикации приложения одним файлом.

Вариант 2 — использовать Serilog.Settings.Configuration для загрузки из appsettings.json.
powershell 
dotnet add package Serilog.Settings.Configuration

Конфигурация доступна не сразу, т.е. мы не сможем отслеживать исключения, возникающие на ранних этапах запуска. Совет — не усложнять себе жизнь, упростив Program.cs до чего-то вроде:
using Serilog;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSerilog(lc => lc
.WriteTo.Console()
.ReadFrom.Configuration(builder.Configuration));

var app = builder.Build();

appsettings.json будет выглядеть примерно так (Seq можно заменить другим хранилищем):
{
"Serilog": {
"Using": ["Serilog.Sinks.Seq"],
"WriteTo": [
{
"Name": "Seq",
"Args": {
"serverUrl": "https://localhost:5341",
"apiKey": "<API key here>"
}
}
]
},
"AllowedHosts": "*"
}

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

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

Источник:
https://nblumhardt.com/2024/04/serilog-net8-0-minimal/
👍16
День 1949. #ЗаметкиНаПолях #BestPractices
Правильное Логирование Минимальных API в Serilog. Окончание

1. Настройка
2. Связываем ASP.NET Core и ILogger<T>

3. Запись веб-запросов
Приложение записывает несколько событий в журнал при обработке каждого веб-запроса. Далее рассмотрим, как объединить эту информацию в одно красиво отформатированное событие.

Классический способ сделать это — установить Serilog.AspNetCore и добавить app.UseSerilogRequestLogging() в код запуска вашего веб-приложения сразу после builder.Build(). Но если вы настраиваете всё с нуля, в долгосрочной перспективе вы получите лучший опыт, полагаясь на встроенную поддержку активности (System.Diagnostics.Activity).

Активность - это операция с началом и концом. ASP.NET Core использует активность как обёртку вокруг каждого обрабатываемого веб-запроса. Т.к. активность завершается после выполнения всего промежуточного ПО, она даёт более точную картину продолжительности запроса и конечного кода состояния, чем может предоставить промежуточное ПО UseSerilogRequestLogging(). Также активности участвуют в распределённой трассировке, поэтому вы можете просматривать входящие и исходящие запросы вашего приложения в иерархии, если выбранный вами приёмник это поддерживает.

1) Установим SerilogTracing, поддержку форматирования вывода консоли и интеграцию с ASP.NET Core:
dotnet add package SerilogTracing
dotnet add package SerilogTracing.Expressions
dotnet add package SerilogTracing.Instrumentation.AspNetCore

2) В Program.cs, добавим средство форматирования с поддержкой трассировки в приёмник консоли и снизим детализацию некоторых источников журналов Microsoft.AspNetCore.*:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override(
"Microsoft.AspNetCore.Hosting",
LogEventLevel.Warning)
.MinimumLevel.Override(
"Microsoft.AspNetCore.Routing",
LogEventLevel.Warning)
.Enrich.WithProperty("Application", "MyApp")
.WriteTo.Console(
Formatters.CreateConsoleTextFormatter(
theme: TemplateTheme.Code))
.WriteTo.Seq(…)
.CreateLogger();

Здесь мы использовали Enrich (хотя это не обязательно), чтобы добавить свойство Application ко всем событиям, поскольку это помогает отслеживать в логах операции среди нескольких сервисов.

3) Сразу после этого настроим прослушиватель, который будет записывать действия ASP.NET Core в конвейер Serilog:
using var listener = 
new ActivityListenerConfiguration()
.Instrument.AspNetCoreRequests()
.TraceToSharedLogger();

SerilogTracing работает со всеми приёмниками Serilog, но на данный момент только приёмники Seq, SerilogTracing.Sinks.OpenTelemetry и SerilogTracing.Sinks.Zipkin были написаны или изменены для поддержки бэкендов с функциями иерархической трассировки.

Совет: если вы используете приемник без поддержки иерархической трассировки, добавьте Enrich.WithSpanTimingMilliseconds(), чтобы добавить свойство Elapsed для событий завершения запроса.

Источник: https://nblumhardt.com/2024/04/serilog-net8-0-minimal/
👍5
Какого асинхронного метода НЕ существует в Entity Framework Core?
#Quiz #EFCore
Anonymous Quiz
13%
CountAsync
4%
ToListAsync
7%
SingleAsync
30%
OrderByAsync
13%
MaxAsync
32%
ForEachAsync
👍40👎16
День 1950. #Оффтоп
.NET Rocks со Скотом Хансельманом

Олды тут? Если вдруг вам нечем заняться в первое воскресенье лета, послушайте юбилейный 1900й выпуск подкаста .NET Rocks с одним из моих любимых популяризаторов программирования вообще и .NET в частности, Скотом Хансельманом.

Три «старичка» в программировании в рамках конференции Microsoft Build весело поболтали о былых временах, о том, как быть «престарелым» (ближе к 50) программистом, идеях и истоках всех подкастов и популярности в соцсетях, а также о том, что для Скотта важно сегодня. Во второй половине Карл Фрэнклин устраивает для Скотта викторину, вспомнит ли он, что он отвечал на вопросы ведущих подкаста 20-лет назад!
- Скотт, почему ты позволяешь писать анонимные комментарии к статьям на своём сайте?
- Я просто не знаю, как установить OAuth.

И множество других замечательных историй о различных конференциях, подкастах и других мероприятиях, а также о том, чему научились в процессе.
👍17
День 1951. #ЧтоНовенького
Вышел Превью Visual Studio 2022 17.11

Microsoft выпустили первый превью Visual Studio 2022 17.11. Вот некоторые новинки.

1. Пул-реквесты
Можно создавать черновики пул-реквестов и создавать описания с помощью шаблонов. Шаблон пул-реквеста по умолчанию будет использоваться при создании нового PR как для GitHub, так и для Azure DevOps. Дополнительную информацию о том, как добавить шаблон пул-реквеста в репозиторий, можно найти в документации GitHub и Azure DevOps.

2. Сочетания клавиш
Добавили Ctrl+/ в качестве альтернативного сочетания клавиш для «закомментирования» строк, которое используется по умолчанию во многих других IDE и редакторах кода.
Для функции поиска инструментов (Feature Search) было добавлено сочетание клавиш Ctrl+Shift+P, которое должно быть знакомо пользователям VS Code для открытия палитры команд.

3. Отладка и профилирование
До сих пор отладка асинхронного кода, особенно в таких средах, как ASP.NET, была сложной из-за возможности возникновения исключений, переходящих через границы асинхронности. Теперь отладчик Visual Studio автоматически прерывается, когда асинхронный метод возвращает исключение в код платформы. Такой подход облегчает выявление и диагностику проблем в приложениях ASP.NET.

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

4. Файлы vsconfig
Используя файлы *.vsconfig, вы можете убедиться, что у вашей команды есть все необходимые компоненты и расширения, необходимые вашему решению. Многие команды используют файлы *.vsconfig для стандартизации установок Visual Studio. Файлы *.vsconfig можно поместить в репозиторий или каталог решения проекта, а Visual Studio теперь при открытии решения автоматически определит, присутствуют ли в установке все необходимые компоненты. Если нет, предложит их установить.

5. Пакеты NPM в обозревателе решений
Теперь вы сможете посмотреть пакеты NPM в узле Dependencies (Зависимости) в обозревателе решений в проектах JavaScript и TypeScript.

Источники:
-
https://www.infoq.com/news/2024/05/vs-2022-preview-1/
-
https://learn.microsoft.com/ru-ru/visualstudio/releases/2022/release-notes-preview
👍19
День 1952. #МоиИнструменты
Операции Перед Коммитом с
Husky.NET. Начало
Если вам нужно выполнить операции перед коммитом в Git, вы можете положиться на перехватчики - Git-хуки (Hooks).

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

Категории Git-хуков:
1. Клиентские на коммит – выполняются при git commit в локальном репозитории;
2. Клиентские на email - выполняются при git am — команды, позволяющей интегрировать почту и репозитории Git (если интересует, вот документация);
3. Клиентские на другие операции - запускаются в локальном репозитории при операциях вроде git rebase;
4. Серверные - запускаются после получения коммита в удалённом репозитории и могут отклонить операцию git push.

Мы сосредоточимся на клиентских хуках на коммит. Они бывают 4х видов:
1. pre-commit - вызывается первым при git commit (если вы не используете флаг -m, то перед запросом о добавлении сообщения) и может использоваться для проверки моментального снимка фиксируемого кода.
2. prepare-commit-msg – может использоваться для редактирования сообщения коммита по умолчанию, когда оно генерируется автоматическим инструментом.
3. commit-msg - может использоваться для проверки или изменения сообщения коммита после его ввода пользователем.
4. post-commit - вызывается после корректного выполнения коммита и обычно используется для запуска уведомлений.

Используем Husky.NET
Для Husky.NET необходимо добавить файл tool-manifest в корень решения:
dotnet new tool-manifest

Эта команда добавит файл .config/dotnet-tools.json со списком всех внешних инструментов, используемых dotnet. Теперь установим Husky:
dotnet tool install Husky

И добавим его в приложение .NET:
dotnet husky install

Это создаст в корне решения папку .husky, содержащую файлы, которые будут использоваться для Git-хуков. Создадим хук:
dotnet husky add pre-commit

Эта команда создаст файл pre-commit (без расширения) в папке .husky. На данный момент он ничего не делает, поэтому изменим его. Следующий текст хука будет компилировать код, форматировать текст (используя правила из файла .editorconfig) и выполнять тесты:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo 'Building code'
dotnet build

echo 'Formatting code'
dotnet format

echo 'Running tests'
dotnet test


Всё готово! Хотя, погодите-ка…

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

Источник:
https://www.code4it.dev/blog/husky-dotnet-precommit-hooks/
👍11
День 1953. #МоиИнструменты
Операции Перед Коммитом с
Husky.NET. Продолжение
Начало

Управляем командой dotnet format с помощью Husky.NET
В приведённом выше примере есть проблема. Т.к. dotnet format изменяет исходные файлы, и учитывая, что моментальный снимок кода уже был создан до выполнения хука, все изменённые файлы не будут частью окончательного коммита! Также dotnet format выполняет проверку каждого файла в решении, а не только тех, которые являются частью текущего снимка. Так операция может занять много времени. Есть 3 подхода к решению этой проблемы.

1. Выполнить git add
Выполнение git add . после dotnet format сделает все изменения частью коммита, но:
- dotnet format всё ещё будет затрагивать все файлы;
- git add . добавит все изменённые файлы в коммит, а не только те, которые вы хотели добавить (возможно потому что другие изменения должны были быть включены в другой коммит).

2. Пробный прогон dotnet format
Флаг --verify-no-changes команды dotnet format приведёт к возвращению ошибки, если хотя бы один файл необходимо обновить из-за правил форматирования.
Таким образом, если есть что форматировать, весь коммит будет отменён. Затем вам придётся запустить dotnet format для всего решения, исправить ошибки, добавить изменения в git и попробовать сделать коммит ещё раз. Это более длительный процесс, но он позволяет вам иметь полный контроль над отформатированными файлами.
Кроме того, вы не рискуете включить в снимок файлы, которые хотите сохранить в промежуточном состоянии, чтобы добавить их в последующий коммит.

3. dotnet format только для файлов коммита с помощью Husky.NET Task Runner
В папке .husky есть файл task-runner.json. Он позволяет создавать собственные сценарии с именем, группой, выполняемой командой и соответствующими параметрами. Изменим его так, чтобы dotnet format затрагивал только файлы, предназначенные для коммита:
{
"tasks": [
{
"name": "dotnet-format-staged-files",
"group": "pre-commit-operations",
"command": "dotnet",
"args": ["format", "--include", "${staged}"],
"include": ["**/*.cs"]
}
]
}

Здесь мы указали имя задачи (dotnet-format-staged-files), команду для запуска (dotnet с параметрами args) и фильтр списка файлов, подлежащих форматированию, используя параметр ${staged}, который заполняется Husky.NET. Мы также добавили задачу в группу pre-commit-operations, которую мы можем использовать для задач, выполняющихся вместе. Так можно запустить отдельную задачу:
dotnet husky run --name dotnet-format-staged-files 

или группу задач:
dotnet husky run --group pre-commit-operations

Теперь заменим команду dotnet format в файле pre-commit на одну из приведённых выше, а также добавим флаги --no-restore для сборки и теста, чтобы ускорить их выполнение:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo 'Format'
dotnet husky run --name dotnet-format-staged-files

echo 'Build'
dotnet build --no-restore

echo 'Test'
dotnet test --no-restore

echo 'Completed pre-commit changes'

Да, сборку можно не делать отдельно, т.к. она запускается перед тестами.

И последнее. Если вы захотите сделать коммит без выполнения хука, используйте флаг --no-verify:
git commit -m "my message" --no-verify


Источник: https://www.code4it.dev/blog/husky-dotnet-precommit-hooks/
👍6