Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
var instance = new MyAsyncClass(); await instance.InitAsync();У такого подхода есть недостатки. Разработчик может забыть вызвать метод
InitAsync, а экземпляр не может использоваться сразу же после выполнения конструктора.
Вот более качественное решение, которое основано на применении паттерна асинхронного фабричного метода:
class AsyncClass
{
private AsyncClass()
{
}
private async Task<AsyncClass> InitAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
return this;
}
public static Task<AsyncClass> CreateAsync()
{
var result = new AsyncClass();
return result.InitAsync();
}
}
Конструктор и метод InitAsync объявлены приватными, чтобы они не могли использоваться в вызывающем коде; экземпляры могут создаваться только одним способом — статическим фабричным методом CreateAsync. Вызывающий код не может обратиться к экземпляру до того, как инициализация будет завершена.
Экземпляр может создаваться следующим образом:
var instance = await AsyncClass.CreateAsync();Главное преимущество этого паттерна заключается в том, что вызывающий код никак не сможет получить неинициализированный экземпляр
AsyncClass.
К сожалению, в некоторых сценариях этот способ не работает — в частности, когда в коде используется провайдер внедрения зависимостей. Ни одна заметная библиотека внедрения зависимостей или инверсии управления не работает с async-кодом. Если вы окажетесь в одной из таких ситуаций, существует пара альтернатив, которые также стоит рассмотреть.
Если создаваемый экземпляр в действительности является общим ресурсом, можно использовать ленивую инициализацию. В противном случае можно воспользоваться паттерном асинхронной инициализации, который рассмотрим позже.
Пример того, как поступать не следует:
class AsyncClass
{
public AsyncClass()
{
InitAsync();
}
// ПЛОХОЙ КОД!!
private async void InitAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
На первый взгляд решение может показаться разумным: вы получаете обычный конструктор, который запускает асинхронную операцию; при этом у него есть ряд недостатков, обусловленных использованием async void. Первая проблема заключается в том, что при завершении конструктора экземпляр всё ещё продолжает асинхронно инициализироваться, и не существует очевидного способа определить, когда завершится асинхронная инициализация. Вторая проблема связана с обработкой ошибок: любые исключения, выданные из InitAsync, не могут быть перехвачены секциями catch, окружающими вызов конструктора объекта.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 11.ContainerBaseImage в файле проекта и выбрать для него новый образ. Процесс сборки также примет имя сборки проекта в качестве имени образа, которое также можно переопределить с помощью свойства проекта ContainerImageName.
Основное ограничение заключается в том, что команды Dockerfile RUN не поддерживаются. По словам Хаска, нет возможности выполнять команды RUN с помощью .NET SDK. Команды Dockerfile RUN позволяют создавать промежуточные образы, запуская команды операционной системы в создаваемом образе, обычно для установки инструмента или изменения конфигурации системы. В качестве обходного пути Microsoft предлагает создать образ, использующий команды RUN, с помощью Docker, а затем указать этот образ в качестве базового при создании контейнеров в .NET SDK.
Чтобы попробовать эту новую функцию в .NET 7, вам необходимо установить Docker на вашем компьютере для разработки, по крайней мере, на данном этапе. Зависимость от Docker связана с тем, что SDK по-прежнему полагается на него для аутентификации в реестрах контейнеров. Также Docker по-прежнему необходим для запуска сгенерированного образа контейнера.
На данный момент эта функция поддерживается только для образов Linux. В Microsoft заявляют, что образы Windows и проверка подлинности реестра контейнеров будут готовы до выпуска .NET 7 наряду с другими возможностями настройки образов.
Источник: https://www.infoq.com/news/2022/09/dotnet-sdk-container-support/public IActionResult Index()
{
var base = $"{Request.Scheme}://{Request.Host}:{Request.Host.Port ?? 80}";
return View();
}
Этот пример обычно предлагается на StackOverflow. Есть некоторые недостатки в использовании такой интерполяции строк. Очевидным является часть порта, которую мы в большинстве случаев не хотим включать. К счастью, в .NET есть встроенный класс для создания URL-адресов с именем UriBuilder. Немного отрефакторим код и включим его в метод расширения HttpRequest:
public static class HttpRequestExtensions
{
public static string? BaseUrl(
this HttpRequest req)
{
if (req == null) return null;
var uriBuilder = new UriBuilder(
req.Scheme,
req.Host.Host,
req.Host.Port ?? -1);
if (uriBuilder.Uri.IsDefaultPort)
uriBuilder.Port = -1;
return uriBuilder.Uri.AbsoluteUri;
}
}
В этом примере мы используем класс UriBuilder для добавления схемы и имени хоста, а порт добавляем только в том случае, если он есть в запросе. Значение -1 (как ни странно) не указывает на отсутствие порта в UriBuilder. Оператор if позволяет избежать отображения номера порта в базовом URL, используется порт по умолчанию для схемы. Т.е., если схема https и порт 443, нам просто нужен https://example.com, а не https://example.com:443.
Теперь для получения базового URL-адреса требуется всего одна строка:
Request.BaseUrl();В Razor Pages у нас не будет доступа к HTTP-запросу, как в MVC. Здесь требуется внедрить HttpContextAccessor в файл Program.cs (Startup.cs):
var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpContextAccessor(); // ... var app = builder.Build(); // ...После этого можно внедрить IHttpContextAccessor на страницу Razor:
{
private readonly IHttpContextAccessor hCA;
public IndexModel(IHttpContextAccessor hCA)
{
this.hCA = hCA;
}
public void OnGet()
{
var baseUrl =
hCA.HttpContext?.Request.BaseUrl();
// ...
}
}
Источник: https://blog.elmah.io/how-to-get-base-url-in-asp-net-core/PublishTrimmed=true, и нарушит работу только тех приложений, для которых ранее выдавались предупреждения при тримминге. Это также влияет только на простые приложения .NET, которые не используют Windows Desktop, Android, iOS, WASM или ASP.NET SDK.
Ранее обрезались только сборки с параметром <IsTrimmable>true</IsTrimmable> в файле проекта библиотеки.
Начиная с .NET 7, по умолчанию обрезаются все сборки в приложении. Приложения, которые ранее могли работать с PublishTrimmed, могут не работать в .NET 7. Однако затронуты будут только приложения с предупреждениями о тримминге. Если ваше приложение ране не выдавало предупреждений о тримминге, изменение поведения не должно вызвать каких-либо неблагоприятных последствий и, скорее всего, лишь уменьшит размер приложения.
Если в вашем приложении были предупреждения о тримминге, вы можете увидеть изменения в поведении или исключения. Например, приложение, использующее Newtonsoft.Json или System.Text.Json без генерации исходного кода для сериализации и десериализации типов в пользовательском проекте, могло функционировать до изменения, поскольку типы в проекте полностью сохранялись. Выдавалось лишь одно или несколько предупреждений о тримминге (коды предупреждений ILxxxx). Теперь типы в пользовательском проекте обрезаются, и сериализация может завершиться ошибкой или привести к непредвиденным результатам.
Причина изменения
Это изменение помогает уменьшить размер приложения без явного согласия пользователей и соответствует ожиданиям пользователей, что всё приложение будет обрезаться, если не указано иное.
Рекомендованное действие
Лучшее решение — устранить все предупреждения о тримминге. Сведения об устранении предупреждений в ваших библиотеках можно посмотреть в разделе документации «Общие сведения о предупреждениях об обрезке».
В отношении сторонних библиотек, обратитесь к автору, чтобы попросить устранить предупреждения, или выберите другую библиотеку, которая уже поддерживает тримминг. Например, вместо Newtonsoft.Json можно переключиться на System.Text.Json с генерацией исходного кода.
Чтобы вернуться к предыдущему поведению, задайте для свойства TrimMode значение partial, которое было поведением по умолчанию до .NET 7:
<TrimMode>partial</TrimMode>Поведение по умолчанию в .NET 7+:
<TrimMode>full</TrimMode>Источник: https://docs.microsoft.com/en-us/dotnet/core/compatibility/deployment/7.0/trim-all-assemblies
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
</PropertyGroup>
</Project>
Теперь вы можете использовать специфический API для Windows, чтобы получить согласие:
using Windows.Security.Credentials.UI;
async Task<bool> ConsentAsync(string reason)
{
var availability = await
UserConsentVerifier.CheckAvailabilityAsync();
if (availability ==
UserConsentVerifierAvailability.Available)
{
var result = await
UserConsentVerifier
.RequestVerificationAsync(reason);
return result ==
UserConsentVerificationResult.Verified;
}
return false;
}
Также можно использовать метод ConsentAsync перед выполнением действия:
if (await ConsentAsync("Confirm your identity"))
Console.WriteLine("Cofirmed");
else
Console.WriteLine("Not confirmed");
Источник: https://www.meziantou.net/getting-user-consent-before-executing-sensitive-code.htminterface ICountBytes
{
Task<int> CountBytesAsync(
HttpClient hc, string url);
}
class Counter : ICountBytes
{
public async Task<int>
CountBytesAsync(HttpClient hc, string url)
{
var b = await hc.GetByteArrayAsync(url);
return b.Length;
}
}
…
async Task UseCounter(
HttpClient hc,
ICountBytes svc)
{
var res = await svc.CountBytesAsync(
hc,
"http://www.google.com");
Console.WriteLine(res);
}
Этот паттерн работает и с абстрактными методами базовых классов.
Асинхронная сигнатура метода (возврат Task или Task<T>) означает лишь то, что реализация может быть асинхронной. Фактическая реализация может быть синхронной, если нет реальной асинхронной работы, которую нужно было бы выполнять. Например, тестовая заглушка может реализовать тот же интерфейс (без async), используя, например, FromResult:
class CounterStub : ICountBytes
{
public Task<int> CountBytesAsync(
HttpClient hc, string url)
{
return Task.FromResult(13);
}
}
Асинхронные методы интерфейсов и базовых классов встречаются всё чаще. Работать с ними не так уж сложно, если помнить, что ожидание должно применяться к возвращаемому типу (а не к методу), а определение асинхронного метода может быть реализовано как асинхронно, так и синхронно.
См. также: Возвращение завершённых задач
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 11.class async {
}
В то время async/await не существовало в .NET, и вы могли бы создать класс, названный с использованием любого из этих ключевых слов, даже если они не соответствовали традиционным стандартам именования PascalCase.
Тут мы решаем обновиться до C# 5, а вместе с ним к нам пришли ключевые слова async/await. Теперь у нас проблемы, потому наш класс имеет конфликт имён.
Microsoft активно выпускает ежегодные выпуски .NET/C#, и само собой разумеется, что они могут добавить дополнительные зарезервированные ключевые слова в язык. Но до этого времени Microsoft, очевидно, не может зарезервировать все возможные комбинации ключевых слов, которые могут понадобиться в будущем.
Поэтому в Microsoft явно указали на тот факт, что все зарезервированные ключевые слова пишутся полностью строчными буквами. И, если вы следуете соглашениям об именовании, которые советуют не называть всё исключительно строчными буквами, то вы можете быть уверены, что в будущем у вас не будет конфликтов.
С другой стороны, это только предупреждение, вы можете его игнорировать, но на свой страх и риск.
Источник: https://dotnetcoretutorials.com/2022/08/10/fixing-cs891-the-type-name-only-contains-lower-cased-ascii-characters/\d означает любую цифру. Что означает термин «цифра», зависит от опций выражения:
- по умолчанию, без опции RegexOptions.ECMAScript, \d означает \p{Nd}, т.е. любой символ из категории «цифр» в Unicode;
- с опцией RegexOptions.ECMAScript \d означает [0-9].
Категория цифр в Unicode содержит цифры 0, 1, 2, … но также цифры из других языков, вроде ٣, ٧, ൩ или ໓. Полный список состоит из 610 символов!
Поэтому
Regex.IsMatch("०१२३४५६७८९", "[0-9]{10}");
вернёт False, тогда как
Regex.IsMatch("०१२३४५६७८९", "\\d{10}");
вернёт True.
Добавление опции решает проблему:
Regex.IsMatch("०१२३४५६७८९", "\\d{10}", RegexOptions.ECMAScript);
вернёт False.
2. [a-zA-Z] или [a-z] с IgnoreCase
Рассмотрим два регулярных выражения:
var regex1 = new Regex("^[a-zA-Z]+$");
var regex2 = new Regex("^[a-z]+$",
RegexOptions.IgnoreCase |
RegexOptions.CultureInvariant);
В большинстве случаев они работают одинаково, так, как мы и предполагаем:
regex1.IsMatch("Test"); // True
regex2.IsMatch("Test"); // True
Однако, есть специальный символ K (U+212A), обозначающий температуру по Кельвину, и здесь всё интереснее:
regex1.IsMatch("K"); // False
regex2.IsMatch("K"); // True
Когда в регулярном выражении указан параметр RegexOptions.IgnoreCase, сравнение между входными данными и шаблоном будет выполняться без учёта регистра. Чтобы поддерживать это, Regex необходимо определить, какие отображения регистра будут использоваться для сравнений. Отображение регистра существует всякий раз, когда у вас есть два символа «A» и «B», где либо:
- «A» является ToLower() представлением «B»,
- оба символа являются представлением в нижнем регистре для одного и того же символа.
В этом случае char.ToLowerInvariant('K') - это k (строчная латинская буква K). Поэтому второе выражение возвращает True для знака Кельвина, однако он не входит в множество [a-zA-Z], поэтому первое выражение возвращает False.
Источник: https://www.meziantou.net/regex-with-ignorecase-option-may-match-more-characters-than-expected.htmpublic class Person
{
public long Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
ExecuteDelete
Чтобы массово удалить набор сущностей, отфильтруем их с помощью Where (как раньше), а затем вызовем ExecuteDelete. Помимо прочего, метод возвращает количество удалённых записей:
var personsDeleted = context.Persons
.Where(p => p.Name.Contains("L"))
.ExecuteDelete();
Вот какой код SQL он сгенерирует:
DELETE FROM [p] FROM [Persons] AS [p] WHERE [p].[Name] LIKE N'%L%'Как видите, он просто генерирует одну инструкцию SQL для удаления сущностей, соответствующих условию. ExecuteUpdate Для обновления сущностей нам нужно использовать новый метод SetProperty. Первый аргумент SetProperty выбирает свойство, которое необходимо обновить с помощью лямбда-выражения, а второй аргумент — это новое значение этого свойства также с помощью лямбда-выражения, в котором мы также можем получать доступ к свойству. Например, здесь мы добавим префикс "New" к имени первой тысячи людей:
var personsUpdated =
context.Persons
.Where(p => p.PersonId <= 1_000)
.ExecuteUpdate(
p => p.SetProperty(
x => x.Name,
x => "New" + x.Name)
);
}
В результате возвращается количество обновлённых записей, а SQL код получается таким:
UPDATE [p] SET [p].[Name] = N'New' + [p].[Name] FROM [Persons] AS [p] WHERE [p].[Id] <= CAST(1000 AS bigint)Чтобы обновить несколько свойств за раз, просто вызовите методы
SetProperty в цепочке:
….ExecuteUpdate(p =>
p.SetProperty(x => x.Name, x => "…")
.SetProperty(x => x.Age, x => 42)
);
}
Источник: https://timdeschryver.dev/blog/new-in-entity-framework-7-bulk-operations-with-executedelete-and-executeupdate