Итак задача:
как создать бесконечно повторяющийся метод в C#, который работает на протяжении работы приложения?
Предположим, метод должен через определенное количество времени выводить какую-то информацию на экран; он должен работать вне зависимости от действий пользователя.
Итак, создаём:
private async Task RunLoopAsync(CancellationToken token)
{
try
{
while (true)
{
// ... что-то сделать
await Task.Delay(1000, token); // подождать одну секунду
}
}
catch (OperationCanceledException)
{ } // сработала отмена, ничего не делать
}
private CancellationTokenSource _cts;
private async void StartLoop()
{
if (_cts != null)
return;
try
{
using (_cts = new CancellationTokenSource())
{
await RunLoopAsync(_cts.Token);
}
}
catch (Exception ex)
{
// ... ex.Message
}
_cts = null;
}
private void StopLoop()
{
_cts?.Cancel();
}
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Необходимо извлечь все URL из атрибутов
href
тегов a
в HTML странице. Можно попробовать воспользоваться регулярными выражениями:Uri uri = new Uri("http://google.com/search?q=test");
Regex reHref = new Regex(@"<a[^>]+href=""([^""]+)""[^>]+>");
string html = new WebClient().DownloadString(uri);
foreach (Match match in reHref.Matches(html))
Console.WriteLine(match.Groups[1].ToString());
Но возникает множество потенциальных проблем:
— Как отфильтровать только специфические ссылки, например, по CSS классу?
— Что будет, если кавычки у атрибута другие?
— Что будет, если вокруг знака равенства пробелы?
— Что будет, если кусок страницы закомментирован?
— Что будет, если попадётся кусок JavaScript?
— И так далее.
Регулярное выражение очень быстро становится нечитаемым. Какие есть другие варианты?
Проверенный игрок на поле парсеров. В отличие от CsQuery, написан с нуля вручную на C#. Также включает парсеры других языков.
API построен на базе официальной спецификации по JavaScript HTML DOM. Изначально содержал в некоторых местах странности, непривычные для разработчиков на .NET (например, при обращении к неверному индексу в коллекции будет возвращён
null
, а не выброшено исключение), но разработчик в конце концов сдался и исправил самые жуткие костыли. Что-то ушло само, например, Microsoft BCL Portability Pack. Что-то осталось, например, пространства имён очень гранулярные, даже базовое использование библиотеки требует три using
и т. п.), но в целом ничего критичного.Обработка HTML простая, к примеру вот:
IHtmlDocument angle = new HtmlParser().ParseDocument(html);
foreach (IElement element in angle.QuerySelectorAll("a"))
Console.WriteLine(element.GetAttribute("href"));
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Humanizer — бесплатная .NET-библиотека с открытым исходным кодом, которая предлагает набор методов для манипулирования строками, числами, датами, временем, временными интервалами, цифрами и величинами в удобном для человеческого восприятия виде.
Работа со временем и датой — одна из самых распространенных и муторных задач в любом приложении. Пользователи ожидают увидеть дату и время в формате, который будет легко восприниматься и соответствовать контексту.
Один из самых простых способов гуманизировать объект
DateTime
— использовать метод расширения Humanize
, который возвращает время и дату на естественном языке. Например:
using Humanizer;
DateTime now = DateTime.Now;
DateTime yesterday = now.AddDays(-1);
DateTime tomorrow = now.AddDays(1);
DateTime nextWeek = now.AddDays(7);
Console.WriteLine(now.Humanize()); // сейчас
Console.WriteLine(yesterday.Humanize()); // вчера
Console.WriteLine(tomorrow.Humanize()); // через 23 часа
Console.WriteLine(nextWeek.Humanize()); // через 6 дней
Как видите, метод
Humanize
возвращает строку, которая легко воспринимается и зависит от текущих времени и даты. Он также учитывает различные временные отношения, такие как сегодня, вчера, завтра, а также будущие или прошлые даты.DateTime
— использовать метод расширения Humanize
с булевым параметром, указывающим, отображать относительное время или нет. Относительное время — это строка, которая представляет, сколько времени прошло с или пройдет до конкретного момента. Например:
using Humanizer;
DateTime anHourAgo = DateTime.Now.AddHours(-1);
DateTime anHourLater = DateTime.Now.AddHours(1);
Console.WriteLine(anHourAgo.Humanize()); // час назад
Console.WriteLine(anHourLater.Humanize()); // через 59 минут
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Вставьте код функции
Mutate
(без использования ключевого слова unsafe
), чтобы на консоль вывелось «404». Сигнатуру функции не менять, замыкания не ловить
const string constStr = "000";
Mutate(constStr);
var nonConst = "000";
Console.WriteLine(nonConst);
void Mutate(string str)
{
// write your code here
}
Вот пример решения, которое полностью устроило лида, который собеседовал кандидата.
void Mutate(string str)
{
GCHandle handle = GCHandle.Alloc(str, GCHandleType.Pinned);
IntPtr pointer = handle.AddrOfPinnedObject();
Marshal.WriteByte(pointer, 0, (byte)'4');
Marshal.WriteByte(pointer, 4, (byte)'4');
handle.Free();
}
Весь смысл кроется в том, как работают строки в .net (интернирование), что такое pinned object и как работать с unmanaged кодом.
Идея интернирования строк состоит в том, чтобы хранить в памяти только один экземпляр типа String для идентичных строк. При старте нашего приложения виртуальная машина создаёт внутреннюю хэш-таблицу, которая называется таблицей интернирования (иногда можно встретить название String Pool).
Pinned Object Heap (POH, Куча Закрепленных Объектов). В отличие от других видов кучи, эта доступна разработчикам явно (что не характерно для сборщика мусора).
Пишите свое решение в комментариях👇
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Асинхронность вызывает большие проблемы у многих, но выход есть — вот:
async/await
— Stephen Cleary. Вводная статьяawait
, но тут как с блоком итератора yield
- код пишется, а руки трясутся.SynchronizationContext
. Начать можно с этой статьи на MSDN. Но если она покажется душной, переходите к пункту 5.TaskScheduler
.@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет на экран это код?
Anonymous Quiz
44%
с :1,4,5,7,2 l :1,4,5,7,3
30%
с :1,4,5,6,7,2 l :1,4,5,7,3
10%
с :1,4,5,6,7,2 l :1,4,5,6,7,2
16%
с :1,4,5,6,7,2,3 l :1,4,5,6,7,2,3
Хотелось бы как-то "накопить" обновления, пока обработчик занят. Причём, сами данные для обновления уже "накапливаются", об этом заботиться не нужно, нужно только разрулить "накопление" запросов к обработчику, чтобы когда обработчик освободиться обрабатывать не все накопившиеся запросы на обновление по отдельности, а за один раз сразу весь пакет "обновлений" обработать.
Вот так происходит сейчас:
async Task UpdateFoo()
{ ...
await SaveAsync();
}
async Task UpdateBar()
{ ...
await SaveAsync();
}
async Task UpdateBaz()
{ ...
await SaveAsync();
}
async Task SaveAsync()
{ // здесь нужно сделать так,
// чтобы одновременно обрабатывался только 1 запрос
// по окончании которого проверялось бы не было ли ещё запросов
// и если были, то обновление запускалось бы ещё 1 раз (сразу за всё "накопленное")
// и так пока есть обновления по окончании очередной обработки
}
Как это можно нормально написать? Для того, чтобы зайти один раз — понятно,
SemaphoreSlim
, видимо (с WaitAsync
). А вот остальное как лучше сделать?System.Threading.Channels.Channel<T>
:private readonly Channel<T> _channel = Channel<T>.CreateUnbounded();
private Task _workerTask;
private async Task WorkerAsync()
{
ChannelReader<T> reader = _channel.Reader;
List<T> list = new();
// ждём здесь, если в канале пусто
while (await reader.WaitToReadAsync())
{
// забираем всё что есть
// ну или можно счётчиком ограничить максимальное количество выгребаемых данных за раз
while (reader.TryRead(out T data))
{
list.Add(data);
}
// пачка собрана, погнали. это можно в try-catch завернуть, чтобы воркер не падал
await UseAsync(list);
list.Clear();
}
}
_workerTask = WorkerAsync();
Чтобы закинуть в канал:
_channel.Writer.TryWrite(x);
WorkerAsync
завершился, нужно вызвать:_channel.Writer.Complete();
await _workerTask;
Вот и все дела
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Относительно недавно на свет появился ASP.NET Core 8.0, и теперь можно обрабатывать ошибки с помощью
IExceptionHandler
. А вот и полезная статья о том, как это делать.
IExceptionHandler
IExceptionHandler
— это интерфейс, который предоставляет разработчику обратный вызов для обработки известных исключений в центральном расположении.IExceptionHandler
реализации регистрируются путем вызова IServiceCollection.AddExceptionHandler
. Время существования экземпляра IExceptionHandler
— одноэлементное. Можно добавить несколько реализаций, и они вызываются в порядке регистрации.Если обработчик исключений обрабатывает запрос, он может вернуться
true
к остановке обработки. Если исключение не обрабатывается обработчиком исключений, то элемент управления возвращается к поведению по умолчанию и параметрам из по промежуточного слоя. Для обработки и необработанных исключений создаются различные метрики и журналы.@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Итак кейс: выполняется некоторый код, выбрасывается исключение
NullReferenceException
со следующим сообщением:Object reference not set to an instance of an object (В экземпляре объекта не задана ссылка на объект)
Что же это значит, и как исправить код?
Вы пытаетесь воспользоваться чем-то, что равно
null
(или Nothing
в VB.NET). Это означает, что либо вы присвоили это значение, либо вы ничего не присваивали.Как и любое другое значение,
null
может передаваться от объекта к объекту, от метода к методу. Если нечто равно null
в методе "А", вполне может быть, что метод "В" передал это значение в метод "А".Если среда выполнения выбрасывает
NullReferenceException
, то это всегда означает: вы пытаетесь воспользоваться ссылкой. И эта ссылка не инициализирована (или уже не инициализирована).Это означает, что ссылка равна
null
, а вы не сможете вызвать методы через ссылку, равную null
, как тут:string foo = null;
foo.ToUpper();
Этот код выбросит исключение
NullReferenceException
на 2 строке, потому что вы не можете вызвать метод ToUpper()
у ссылки на string
, равной null
.Как определить источник ошибки?
Общие рекомендации: поставьте точки останова в ключевых местах, изучите значения переменных, расположив курсор мыши над переменной, либо открыв панели для отладки: Watch, Locals, Autos.
Если вы хотите определить место, где значение ссылки устанавливается (или нет), нажмите ПКМ на её имени и выберите "Find All References". Затем вы можете поставить точки останова на каждой найденной строке и запустить приложение в режиме отладки. Каждый раз, когда отладчик остановится на точке останова, вы можете удостовериться, что значение верное.
Так вы придёте к месту, где значение ссылки не должно быть
null
, и определите, почему не присвоено верное значение.@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
В частности, в ролике идёт речь о таких вещах как:
— Улучшает ясность и понятность кода, делая его самодокументируемым.
Пример: система управления контентом, где переменные и методы не дают ясного понимания их назначения.
— Разделение определения операции от ее реализации, улучшение модульности, упрощение тестирования и увеличение гибкости кода.
Пример: система обработки платежей, где класс реализует методы для каждого типа платежа, но добавление новых способов обработки может быть проблематичным.
— Улучшение читаемости и поддержки кода, избегая сложных и больших условных выражений.
Пример: система управления заказами, где много уровней вложенности и проверок, что затрудняет чтение и понимание кода.
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Возможно, вы в курсе, что этот учебник существует. В любом случае, это отличный способ ознакомиться с C# и .NET
Пока что заданий в нём не много, а оглавление выглядит так:
— Запуск первой программы C#
— Объявление и использование переменных
— Работа со строками
— Другие действия со строками
— Строки поиска
— Выполнение задачи
Учебник будет развиваться и пополняться новыми заданиями, что очень неплохо
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Держите полезную статью о том, как реализована асинхронность в C#.
О чём статья?
ExecutionContext
в .NET Core теперь является неизменяемым, что упрощает передачу контекста и делает его передачу более распространенной.CallContext
в .NET Core больше не существует, а его функции моделируются через AsyncLocal<T>
ThreadPool
реализована как ConcurrentQueue<T>
, что позволяет оптимизировать выделение памяти.AwaitUnsafeOnCompleted
в .NET Core отличается от .NET Framework, что приводит к более эффективному использованию памяти.@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
«А что там собственно нового в C# 12?»
Интересное видео, в котором Евгений Федотов рассказывает о нововведениях в C# 12
О чём вообще речь?
⏩ Упрощение определения типов, использование точки с запятой вместо фигурных скобок.
⏩ Collection-оператор, объединение массивов в одну коллекцию.
⏩ Атрибут эксперимента, для указания на экспериментальный код.
⏩ Атрибут для перехвата методов.
⏩ Использование перехватчиков для логирования и других целей.
⏩ Возможность указывать функциональные значения для лямда-выражений.
⏩ Возможность выдавать псевдоним любому типу или кортежу.
⏩ и ещё освещается много всего, так что будет полезно
📎 Кликабельный план видео
📎 Видео
@csharp_ci
Интересное видео, в котором Евгений Федотов рассказывает о нововведениях в C# 12
О чём вообще речь?
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM