Что выведет на экран этот код?
Anonymous Quiz
24%
10 10
45%
10 20
31%
Возникнет ошибка времени выполнения
null
или пустую коллекцию? Есть ли общепринятая практика?На запрос «дайте мне список всех пользователей» пустая коллекция имеет очевидный смысл: «окей, вот вам список всех пользователей, их ровно 0 штук».
С другой стороны, возвращённый
null
может означать что угодно: «я не знаю, сколько пользователей», «количество пользователей ещё не подсчитано», «текущая база данных вообще не имеет понятия пользователь», что угодно.То есть смысл пустой коллекции всегда ясен, смысл
null
не определён.null
— так вы избавите пользователя от необходимости писать утомительный код проверок на null
и, соответственно, избавите его от большого количества NullReferenceException
, если он таковой код написать забыл. Хорошим примером могут быть случаи, когда коллекции задействованы в цепочках методов видаvar res = FirstMethod().SecondMethod().ThirdMethod();
и в случае, если коллекция равна
null
, вся цепочка может быть обрушена исключением, тогда как проверки на null
превратят этот лаконичный и довольно элегантный синтаксис в кашу из вложенных if
-ов. Для коллекций это можно считать общепринятой практикой.Кстати, в LINQ именно так и делается. Например, следующий код вполне нормально работает без выбрасывания исключений, хотя очевидно, что ни один из элементов коллекции не удовлетворяет условию лямбды в
Where
:var res2 = new[] { 1, 2, 3 }.Where(x => x > 10).Select(x => x);
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Если вы хотите глубже разобраться, как работать с элементами ProgressBar и BackgroundWorker в C#, с элементом FolderBrowserDialog, а также с событиями DoWork, ProgressChanged, RunWorkerCompleted, то большая часть этой статьи может принести вам много нового опыта.
Уверен, будет полезно)
Будущее приложение по поиску файлов будет выглядеть как на изображении
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
out
даёт такой же результат, как и с ref
.Такой код:
private void func(out string value)
{
value = "Hello World!";
}
Даёт такой же эффект, как и:
private void func(ref string value)
{
value = "Hello World!";
}
В чём же разница между
out
и ref
?out
— это выходной параметр, а ref
— входно-выходной.Для
ref
-параметра вы должны передать его инициализированным, и можете пользоваться его исходным значением. А для out
-параметра вы не обязаны инициализировать его перед вызовом функции, не можете использовать его значение в функции до присваивания, и обязаны инициализировать его в функции.(Таким образом,
ref
-параметр немного напоминает инициализированную локальную переменную, а out
-параметр — неинициализированную.)Иллюстрация:
private void func1(out string value)
{
Console.WriteLine(value); // нельзя, value не инициализировано
if (false)
return; // нельзя, забыли установить значение value
value = "Hello World!";
}
string s1;
func1(out s1);
private void func2(ref string value)
{
Console.WriteLine(value); // можно
if (false)
return; // не проблема, у value остаётся старое значение
value = "Hello World!";
}
string s2;
func2(ref s2); // нельзя, функция имеет право использовать значение,
// значит, оно должно быть инициализировано сначала
Таким образом,
out
-параметр — это как бы дополнительное возвращаемое значение функции. А ref
-параметр — просто параметр, изменения которого видны снаружи функции.@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
File
:[HttpGet("file/{guid}/download")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> DownloadFile([FromRoute] Guid guid)
{
var file = await _mediaContentService.DownloadFile(guid);
return File(file.Stream, file.ContentType, file.FileName);
}
Можно ли каким-либо образом реализовать ограничение на скачивание файла, чтобы, например, в единицу времени не больше N пользователей качало файл?
File
— он возвращает ленивый ответ. Т.е. он не читает весь поток сразу, а ждет пока будет вызван метод от IActionResult
.Чтобы эту проблему решить, надо знать, когда файл точно отправлен.
Можно сделать специальный декоратор. Например:
[ApiController]
[Route("[controller]")]
public class SampleController : ControllerBase
{
private readonly IRateLimiter _rateLimiter;
public SampleController(IRateLimiter rateLimiter)
{
_rateLimiter = rateLimiter;
}
[HttpGet("connection")]
public async Task<IActionResult> DownLoadFile(Guid file)
{
var stream = await GetFileStream(file);
return new RateLimiterFileActionResult(File(stream, "content/type", "sample.txt"), _rateLimiter);
}
}
class RateLimiterFileActionResult : IActionResult
{
private readonly IActionResult _actionResultImplementation;
private readonly IRateLimiter _rateLimiter;
public RateLimiterFileActionResult(IActionResult actionResultImplementation, IRateLimiter rateLimiter)
{
_actionResultImplementation = actionResultImplementation;
_rateLimiter = rateLimiter;
}
public async Task ExecuteResultAsync(ActionContext context)
{
try
{
await _rateLimiter.ObtainAsync(context.HttpContext.RequestAborted);
await _actionResultImplementation.ExecuteResultAsync(context);
}
finally
{
await _rateLimiter.ReleaseAsync(context.HttpContext.RequestAborted);
}
}
}
public interface IRateLimiter
{
public Task ObtainAsync(CancellationToken token);
public Task ReleaseAsync(CancellationToken token);
}
Стоит заметить, что блокировка берётся внутри метода декоратора, а не в методе контроллера.
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
В этой статье мы разберёмся с запуском C#-тестов на Linux и последующую публикацию их в открытом репозитории кода на GitHub.
План:
├╼
Устанавливаем .NET на Ubuntu 22.04├╼
Создаём проект├╼
Запускаем сборку в GitHub Actions├╼
Используем секреты в коде тестов╰╼
Размещаем секреты в GitHub Actions Secrets@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет этот код?
Anonymous Poll
25%
True
32%
False
34%
Возникнет ошибка компиляции
9%
Возникнет ошибка времени выполнения
Повторение — мать учения и основа научного метода, так что приступим)
Task.Yield()
возвращает специальное значение, предназначенное для передачи оператору await
, и в отрыве от этого оператора не имеющее смысла.Конструкция же
await Task.Yield()
делает довольно простую вещь — прерывает текущий метод и сразу же планирует его продолжение в текущем контексте синхронизации.Используется же эта конструкция для разных целей.
protected override async void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
await Task.Yield();
// (какая-то логика)
}
using (var ts = new TransactionScope()) {
// ...
Foo();
// ...
ts.Complete();
}
async void Foo() {
// ... тут мы находимся в контексте транзакции
if (Transaction.Current != null) await Task.Yield();
// ... а тут его уже нет!
}
Например, рассмотрим упрощенную реализацию
AsyncLock
:class AsyncLock
{
private Task unlockedTask = Task.CompletedTask;
public async Task<Action> Lock()
{
var tcs = new TaskCompletionSource<object>();
await Interlocked.Exchange(ref unlockedTask, tcs.Task);
return () => tcs.SetResult(null);
}
}
Здесь поступающие запросы на получение блокировки выстраиваются в неявную очередь на продолжениях. Казалось бы, что может пойти не так?
private static async Task Foo()
{
var _lock = new AsyncLock();
var unlock = await _lock.Lock();
for (var i = 0; i < 100000; i++) Bar(_lock);
unlock();
}
private static async void Bar(AsyncLock _lock)
{
var unlock = await _lock.Lock();
// do something sync
unlock();
}
Здесь продолжение метода
Bar
вызывается в тот момент, когда другой метод Bar
выполняет вызов unlock()
. Получается косвенная рекурсия между методом Bar
и делегатом unlock
, которая быстро сжирает стек и ведет к его переполнению.Добавление же вызова
Task.Yield()
перенесет исполнение в "чистый" фрейм стека, и ошибка исчезнет:class AsyncLock
{
private Task unlockedTask = Task.CompletedTask;
public async Task<Action> Lock()
{
var tcs = new TaskCompletionSource<object>();
var prevTask = Interlocked.Exchange(ref unlockedTask, tcs.Task);
if (!prevTask.IsCompleted)
{
await prevTask;
await Task.Yield();
}
return () => tcs.SetResult(null);
}
}
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
int[] simpleArray = {0, 1, 2, 3, 4, 5, 6, 7, 8};
ref int simpleElementRef = ref simpleArray[3];
В этом примере
simpleElementRef
— ссылка на simpleArray[3]
. Изменение simpleElementRef
приводит к изменению элемента массива:simpleElementRef *= 15;
Console.WriteLine(simpleElementRef); // 15
Console.WriteLine(simpleArray[3]); // 15
Ссылочные локальные переменные используются для специальных сценариев микро-оптимизации и как правило применяются в сочетании с возвращаемыми ссылочными значениями.
static string X = "Старое значение";
static ref string GetX() => ref X; // Возвращает ссылочное значение
static void Main()
{
ref string xRef = ref GetX(); // Присваивает результат ссылочной локальной переменной
xRef = "Новое значение";
Console.WriteLine(X); // Выводит Новое значение
}
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
• Конкурентное исполнение
• Параллельное исполнение
• Многопоточное исполнение
• Асинхронное исполнение
Каждый из этих терминов строго определен и имеет четкое значение.
Конкурентность — это наиболее общий термин, который говорит, что одновременно выполняется более одной задачи.
Параллельное исполнение подразумевает наличие более 1 вычислительного устройства (например, процессора), которые будут одновременно выполнять несколько задач.
Параллельное исполнение — это строгое подмножество конкурентного исполнения. Это значит, что на компьютере с 1 процессором параллельное программирование - невозможно
Многопоточность — это один из способов реализации конкурентного исполнения путем выделения абстракции "рабочего потока" (worker thread).
Потоки "абстрагируют" от пользователя низкоуровневые детали и позволяют выполнять более чем одну работу "параллельно". Операционная система, среда исполнения или библиотека прячет подробности того, будет многопоточное исполнение конкурентным (когда потоков больше чем физических процессоров), или реально параллельным.
Асинхронность подразумевает, что операция может быть выполнена кем-то на стороне: удаленным веб-узлом, сервером или другим устройством за пределами текущего вычислительного устройства.
• Вы взяли лопату и копаете. Это однопоточная работа
• Вы пригласили друга Васю и копаете вместе, периодически задевая друг-друга лопатами. Это многопоточная работа
• Пока вы копаете бассейн, Вася копает канаву под водопровод. Никто никому не мешает. Это распараллеливание
• Вы пригласили бригаду землекопов, а сами с Васей пошли отдыхать. Когда бригада все сделает, к вам придут за деньгами. Это асинхронная работа.
Количество лопат в хозяйстве - это количество ядер в системе
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Очень понятное объяснение, что же такое Zenject, Dependency Injection и всё такое
Рекомендую)
Что внутри?
• 00:36 - Другие годные источники по зенджекту
• 02:20 - Что такое Dependency Injection?
• 03:39 - Проблема DI — большое количество сервисов
• 04:02 - Проблема DI — вложенные зависимости
• 04:56 - Проблема DI — разное время жизни сервисов
• 05:45 - Что делает Zenject?
• 06:35 - Binding в Zenject
• 07:54 - Installer-s в Zenject
• 09:16 - Context в Zenject
• 10:54 - Как это всё работает
• 11:54 - Дополнительные инструменты Zenject-а
• 13:04 - ITickable, IInitializable и IDisposable
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
В какой строке возникнет первое исключение DivideByZeroException?
Anonymous Quiz
37%
#1
22%
#2
29%
#3
11%
Ни в одной
Семафоры позволяют ограничить количество потоков, которые имеют доступ к определенным ресурсам. В .NET семафоры представлены классом
Semaphore
.Для создания семафора применяется один из конструкторов класса
Semaphore
:Semaphore (int initialCount, int maximumCount)
параметр
initialCount
задает начальное количество потоков, а maximumCount
— максимальное количество потоков, которые имеют доступ к общим ресурсам: initialCount
— изначальное доступное количество свободных мест, maximumCount
— вместимость.Semaphore (int initialCount, int maximumCount, string? name)
в дополнение задает имя семафораSemaphore (int initialCount, int maximumCount, string? name, out bool createdNew):
последний параметр — createdNew
при значении true
указывает, что новый семафор был успешно создан. Если этот параметр равен false
, то семафор с указанным именем уже существуетДля работы с потоками класс
Semaphore
имеет 2 основных метода:—
WaitOne()
ожидает получения свободного места в семафоре—
Release()
освобождает место в семафореВ отличие от
lock (Monitor)
и Mutex
, у Semaphore
нет «владельца» — он не зависит от потока. Любой поток может вызвать Release
на семафоре, тогда как с Mutex
и блокировкой только поток, получивший блокировку, может ее освободить.// запускаем пять потоков
for (int i = 1; i < 6; i++)
{
Reader reader = new Reader(i);
}
class Reader
{
// создаем семафор
static Semaphore sem = new Semaphore(3, 3);
Thread myThread;
int count = 3;// счетчик чтения
public Reader(int i)
{
myThread = new Thread(Read);
myThread.Name = $"Читатель {i}";
myThread.Start();
}
public void Read()
{
while (count > 0)
{
sem.WaitOne(); // ожидаем, когда освободиться место
Console.WriteLine($"{Thread.CurrentThread.Name} входит в библиотеку");
Console.WriteLine($"{Thread.CurrentThread.Name} читает");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.Name} покидает библиотеку");
sem.Release(); // освобождаем место
count--;
Thread.Sleep(1000);
}
}
}
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM