.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
День пятьсот сорок седьмой.
7 Опасных Ошибок, Которые Легко Совершить в C#/.NET. Продолжение 3-4
В каждом языке есть ловушки, в которые легко попасть и ошибочные представления об ожидаемом поведении языка. Вот некоторые из них.
Начало 1-2.

3. Игнорирование Безопасности Потоков
Многопоточность великолепна. При правильной реализации может произойти значительное улучшение производительности вашего приложения. Однако вы должны быть осторожны с объектами, которые вы изменяете, потому что могут возникать, на первый взгляд, случайные ошибки.

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

В качестве примера возьмем этот блок кода, который использует простой, но не потокобезопасный List<T>:
var items = new List<int>();
var tasks = new List<Task>();
for (int i = 0; i < 5; i++) {
tasks.Add(Task.Run(() => {
for (int k = 0; k < 10000; k++)
items.Add(i);
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(items.Count);

Мы добавляем числа от 0 до 4 в список по 10000 раз каждое, т.е. мы должны получить список в 50000 элементов, верно? Ну, есть небольшой шанс, что так и получится, но ниже мои результаты 5 выполнений:
28191
23536
44346
40007
40476
Попробуйте сами

Так получается, потому что метод Add не является атомарным, т.е. поток может «прервать» исполнение метода. То есть размер массива может измениться, в то время как другой поток находится в процессе добавления элемента, либо два потока могут добавлять элементы с одним индексом. Иногда могут возникать исключения IndexOutOfRange или ArgumentException, вероятно, потому что размер массива изменился во время добавления к нему элемента. Что делать? Мы могли бы использовать ключевое слово lock, чтобы гарантировать, что только один поток может добавлять элемент в список одновременно, но это может значительно повлиять на производительность. Microsoft любезно предоставляет несколько потрясающих коллекций, которые безопасны для потоков и оптимизированы для производительности.

4. Неправильное Округление
Теперь о чём-то более простом. .NET имеет прекрасный статический метод Round в классе Math, который принимает числовое значение и округляет до заданного десятичного знака. Работает обычно идеально, но что делать, когда вам нужно округлить 2.25 до десятых?
Math.Round(2.25, 1);
Вероятно, вы ожидаете, что это округлится до 2.3 – ведь нас так учили в школе. Однако, похоже, .NET использует «округление банкиров» и округляет приведенный пример до 2.2! Банкиры в таком случае округляют до ближайшей чётной цифры. К счастью, это можно легко переопределить в методе Math.Round:
Math.Round(2.25, 1, MidpointRounding.AwayFromZero);
По умолчанию используется MidpointRounding.ToEven.

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

Источник:
https://chrisstclair.co.uk/7-dangerous-mistakes-in-c-net-that-are-easy-to-make/
День пятьсот сорок восьмой.
7 Опасных Ошибок, Которые Легко Совершить в C#/.NET. Продолжение 5-6
В каждом языке есть ловушки, в которые легко попасть и ошибочные представления об ожидаемом поведении языка. Вот некоторые из них.
Начало 1-2
Продолжение 3-4

5. Ужасный Класс "DBNull"
У некоторых это может вызвать неприятные воспоминания, т.к. ORM скрывает это зло от нас, но если вы используете чистый ADO.NET (SqlDataReader и т.п.), вы рано или поздно столкнётесь с DBNull.Value.

Я не уверен на 100%, почему в Microsoft решили представить NULL в виде специального типа DBNull со статическим полем Value. Возможно, причина в обратной совместимости. Так было в Visual Basic в эпоху до .NET. VB6 не имеет унифицированной объектной модели (когда всё наследуется от System.Object) или концепции «null». Существует Nothing, но это скорее default из C#, чем null. Таким образом, в VB6 и ADO, у вас есть значение DBNull для представления null из БД.

Разница может показаться незначительной, но значения DBNull и null имеют разную семантику. Например, в строках. В VB нулевая строка равна пустой строке. Нет никакого способа разделить эти две концепции как в VB6, так и VB7+ (VB.NET).

Единственное преимущество этого - вы не получите NullReferenceException при доступе к полю базы данных, равному NULL. Однако при этом вы не только должны поддерживать отдельный способ проверки значений на NULL (о чём можно легко забыть, и что может привести к серьезным ошибкам), но также вы теряете прекрасные возможности языка C#. Например, вместо:
reader.GetString(0) ?? "NULL";
придётся использовать:
reader.GetString(0) != DBNull.Value ? reader.GetString(0) : "NULL";
Ужас!

6. Злоупотребление ленивой загрузкой в LINQ
Ленивая загрузка - это отличная функция как LINQ to SQL, так и LINQ to Entities (Entity Framework), которая позволяет загружать связанные данные таблицы по требованию. Допустим, у нас есть таблицы Modules и Results с отношением «один ко многим» (модуль может иметь много результатов).

Когда нужно получить данные определённого модуля, но не нужны относящиеся к нему результаты, Entity Framework достаточно умён, чтобы запросить данные только для модуля, а для получения результатов выполнить отдельный запрос, только когда они потребуются. Таким образом, приведенный ниже код будет выполнять как минимум 2 запроса: один для получения модуля, а другой для получения результатов (для каждого модуля).
using (var db = new DBEntities()) {
var modules = db.Modules;
foreach (var module in modules) {
var moduleType = module.Results;
//Обработка модуля
}
}

Однако, если у нас много модулей, отдельный SQL запрос к результатам будет выполняться для каждого модуля. Очевидно, что это лишняя и ненужная нагрузка на сервер. Но в Entity Framework решить проблему очень легко, просто запросив включение результатов в исходный запрос:
var modules = db.Modules.Include(b => b.Results);
В этом случае выполнится всего один SQL запрос, который будет включать все модули и все результаты для каждого модуля, что Entity Framework потом корректно транслирует в объекты модели.

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

Источник:
https://chrisstclair.co.uk/7-dangerous-mistakes-in-c-net-that-are-easy-to-make/
День пятьсот сорок девятый.
Хороших выходных вам, дорогие подписчики. И вот вам видосик скрасить досуг первого дня авгрусти (запасайтесь двумя часами). На этот раз (большая редкость) видео на русском. Андрея Акиньшина в мире .NET лишний раз представлять не нужно. Его (по крайней мере, в русскоязычном сообществе) должны знать примерно как Рихтера или Скита. Впрочем, авторы видео его и не представляют, поэтому, если вы таки не знаете, кто это, сначала погуглите)))
В интервью каналу «Мы обречены» он рассказывает о своей книге «Pro .NET Benchmarking», про производительность и красоту кода, про хобби программистов и многое другое. В общем, лично мне очень понравилось.
День пятьсот пятидесятый.
7 Опасных Ошибок, Которые Легко Совершить в C#/.NET. Окончание 7
В каждом языке есть ловушки, в которые легко попасть и ошибочные представления об ожидаемом поведении языка. Вот некоторые из них.
Начало 1-2
Продолжение 3-4
Продолжение 5-6

7. Непонимание Того как LINQ to SQL/Entity Framework Генерирует Запросы
Продолжая тему из пункта 6, стоит упомянуть, насколько по-разному будет выполняться ваш код, если он находится внутри запроса LINQ. Если коротко, весь код внутри запроса LINQ транслируется в SQL. Это кажется очевидным, но очень легко забыть контекст, в котором вы находитесь и допустить ошибку. Ниже приведён список некоторых типичных проблем, с которыми вы можете столкнуться.

Большинство методов не будут работать
Представьте, что вы используете следующий запрос, чтобы разделить имя модулей по двоеточию и захватить вторую часть:
var modules = from m in db.Modules
select m.Name.Split(':')[1];
Это вызовет исключение в большинстве провайдеров LINQ: для метода Split нет перевода в SQL. Могут поддерживаться некоторые методы, например, добавление дней к дате, но всё зависит от провайдера.

Методы, которые всё же сработают, могут дать неожиданные результаты…
Рассмотрим следующее выражение LINQ (понятия не имею, зачем это делать на практике, но просто допустим, что надо):
int modules = db.Modules.Sum(a => a.ID);
Если строки в таблице Modules есть, то выражение выдаст сумму значений идентификаторов. Вроде всё правильно. Но что, если применить это выражение к LINQ to Objects? Мы можем сделать это, преобразовав коллекцию модулей в список перед вызовом метода Sum:
int modules = db.Modules.ToList().Sum(a => a.ID);
И ВНЕЗАПНО!… мы получим тот же ответ. Однако, если строк в таблице не будет, то LINQ to Objects вернёт 0, а версия для Entity Framework/LINQ to SQL выдаст исключение InvalidOperationException, сообщающее, что невозможно преобразовать int? в int. Это связано с тем, что вызов SUM в SQL на пустом наборе вместо 0 возвращает NULL. Следовательно, вместо этого он пытается вернуть обнуляемый тип int. Поэтому…

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

Источник: https://chrisstclair.co.uk/7-dangerous-mistakes-in-c-net-that-are-easy-to-make/
День пятьсот пятьдесят первый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
53. Модульные тесты - пустая трата времени?
Модульные тесты - пустая трата времени: вы всегда будете тратить больше усилий на их поддержку, чем на написание кода.
- Кто-то, в какой-то момент вашей карьеры

Я слышал это десятки раз. Обычно веб-разработчики более склонны так думать, чем их бэкенд-коллеги. И на то есть веская причина: юнит-тестам фронтэнда ещё есть куда расти. В последние годы мне посчастливилось запустить большое количество новых проектов, больших и малых, и, оглядываясь назад, я чувствую, что написание тестов всегда экономило время (или могло бы сэкономить). Но мне потребовалось много времени, чтобы по-настоящему понять их преимущества и изменить свои привычки. Есть много тех, кто до сих пор скептически относится к юнит-тестам, но вот что лично я бы сказал молодому себе: «Забудь про фразу "не хватает времени для написания модульных тестов"».

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

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

Модульное тестирование - это не какая-то дополнительная фича, которую хорошо бы иметь. Это обязательный шаг в процессе разработки программного обеспечения. Как команда инженеров, вы можете пропустить их, но это рано или поздно отбросит вас назад и будет стоить вам кучу времени. В конце концов, а какие есть другие варианты?
1. Не тестировать вообще. Излишне говорить, что полученный продукт будет бесполезным дерьмом. Вы должны будете исправлять его исключительно на основании отзывов пользователей, что приведёт к непостижимым затратам и поиску другой работы.
2. Отдать продукт на ручное тестирование другой команде. Достаточно скоро вы обнаружите, что проблемы, которые в противном случае вы могли бы обнаружить довольно быстро, будут продолжать возникать между вами и тестерами. Это уже лучше: по крайней мере ваши пользователи больше не проводят тестирование за вас. Тем не менее, постоянный пинг-понг тикетами с тестировщиками отнимает много времени, плюс с каждой итерацией вам придётся перезапускать весь процесс.
3. Вы вручную тестируете свой код перед тем, как передать его по дальше. Это, наверное, самый распространенный сценарий (по крайней мере, я на это надеюсь). Вы наверняка думаете, что как разработчик вы хорошо знаете, где найти проблему и быстро её исправить. Однако, учитывая, насколько сложным может быть состояние приложения и насколько долго иногда приходится воссоздавать все необходимые условия для репликации проблемы, в конце концов вы потратите уйму времени, не на исправление ошибки, а тыкая по кнопкам, воспроизводя путь пользователя.

Да, модульное тестирование в любом случае отнимает много времени, особенно если у вас плохо спроектирована кодовая база. Но вам всё равно придётся тестировать свой код. Единственный выбор, который перед вами стоит, - тратить время, выполняя тесты вручную, или инвестировать некоторые усилия в обучение правильному написанию модульных тестов и получать выгоду позже.

Источник: https://hackernoon.com/are-unit-tests-a-waste-of-your-time-ftw3umv
День пятьсот пятьдесят второй. #ЧтоНовенького
Новые Функции в Visual Studio для Повышения Продуктивности. Начало
Команда Roslyn представляет новые функции для повышения продуктивности в Visual Studio 2019.

1. Автодополнения IntelliSense для форматов DateTime и TimeSpan.
Эта обещает быть чрезвычайно полезным, потому что мы все знаем, как сложно запоминать форматы даты и времени. Поместите курсор в строковый литерал DateTime или TimeSpan и нажмите (Ctrl+Пробел). Вы увидите варианты завершения и объяснение, что означает каждый символ. См. рис.1 ниже.

2. Добавление заголовка файла позволяет легко добавлять заголовки к существующим файлам, проектам и решениям с помощью EditorConfig. Добавьте правило file_header_template в файл .editorconfig и установите нужный текст заголовка. Затем поместите курсор в первую строку любого файла C#. Нажмите Ctrl+., чтобы вызвать меню «Быстрые действия и рефакторинг», и выберите «Добавить заголовок файла» («Add file header»). См. рис.2 ниже.

3. Диалоговое окно изменения сигнатуры метода теперь позволит добавлять параметры. Поместите курсор на сигнатуру метода и Ctrl+., чтобы вызвать меню «Быстрые действия и рефакторинг», и выберите «Изменить сигнатуру» («Change signature»). Откроется окно, где вы можете нажать «Добавить» («Add») для добавления параметра. При этом откроется новое диалоговое окно «Добавить Параметр» («Add Parameter»). См. рис.3 ниже. Оно позволяет задать тип и имя параметра, а также сделать параметр обязательным или необязательным со значением по умолчанию. Кроме того, вы можете задать значение в вызывающем коде (по желанию использовав именованный аргумент для этого значения), либо ввести переменную TODO. Переменная TODO поместит «TODO» в вызывающий код, чтобы вы могли обойти все вызовы метода и передать нужное значение в каждом случае. Для необязательных параметров есть возможность пропустить изменения в вызывающем коде.

Источник: https://devblogs.microsoft.com/dotnet/learn-about-the-latest-net-productivity-features/
День пятьсот пятьдесят третий. #юмор
Примерно так же надо задавать вопрос на Stackoverflow)))
День пятьсот пятьдесят четвёртый. #ЧтоНовенького
Новые Функции в Visual Studio для Повышения Продуктивности. Окончание
Начало

Исправления и рефакторинг кода - это предложения по изменению кода, которые компилятор предоставляет с помощью значков лампочки и отвертки. Чтобы вызвать меню быстрых действий и рефакторинга, нажмите Ctrl+. или Alt+Enter. Команда Roslyn представляет новые возможности исправления и рефакторинга кода в Visual Studio 2019:

- Добавление явного приведения (Add explicit cast). Позволяет добавить оператор явного приведения, если неявное приведение выражения невозможно. См. рис. 1 ниже.

- Упрощение условного выражения (Simplify conditional expression). Делает условные выражения более чёткими и краткими. См. рис. 2 ниже.

- Преобразование в дословную/обычную строку (Convert To Verbatim/Regular String). Позволяет в одно нажатие преобразовать строку из обычной в дословную и наоборот. См. рис. 3 ниже.

- Добавление атрибута «DebuggerDisplay» (Add ‘DebuggerDisplay’ attribute). Предложение появляется, если поместить курсор на имя класса. Позволяет программно закреплять свойства в отладчике кода. Добавляет атрибут отображения в отладчике в начало класса и генерирует автоматический метод, который возвращает ToString(). Метод можно отредактировать, чтобы вернуть значение свойства, которое вы хотите закрепить в отладчике. См. рис. 4 ниже.

- Генерация операторов сравнения (Generate comparison operators). Генерирует шаблонный код с операторами сравнения для типов, реализующих IComparable. См. рис. 5 ниже.

- Генерация операторов IEquatable (Generate Equals(object)). Добавляет реализацию IEquatable, а также операторы равенства и не равенства для структур. См. рис. 6 ниже.

- Генерация конструктора со свойствами (Generate constructor in <QualifiedName> (with properties)). Предложение появляется, если поместить курсор на экземпляр типа. Позволяет добавить в соответствующий тип конструктор нужной сигнатуры и необходимые свойства. См. рис. 7 ниже.

- Исправление случайных присвоений и сравнений (Assign/Compare to '<QualifiedName>.value'). Позволяет исправить операцию присвоения или сравнения, когда имена поля класса и параметра конструктора совпадают. Исправление добавляет “this.” в нужную часть операции. См. рис. 8 ниже.

Источник: https://devblogs.microsoft.com/dotnet/learn-about-the-latest-net-productivity-features/
День пятьсот пятьдесят пятый. #DotNetAZ
Dotnet A-Z. 5
В этой серии постов рассмотрим наиболее распространённые понятия в .NET в алфавитном порядке.

Mono
Mono - это кроссплатформенная реализация .NET с открытым исходным кодом, которая в основном используется, когда требуется небольшая среда выполнения. Это среда выполнения, которая поддерживает приложения Xamarin на Android, Mac, iOS, tvOS и watchOS и ориентирована в первую очередь на приложения, чувствительные к размеру исполняемого файла.
Mono поддерживает все опубликованные в настоящее время версии .NET Standard. Исторически Mono реализовывал более крупный API .NET Framework и эмулировал некоторые из самых популярных возможностей Unix. Иногда он используется для запуска приложений .NET, которые полагаются на эти возможности в Unix. Mono обычно используется совместно с JIT-компилятором, но он также имеет полноценный AOT-компилятор, который используется например в iOS.
Документация Mono

.NET
Общий термин для .NET Standard и всех реализаций .NET. Термин всегда пишется всеми заглавными буквами.
Документация .NET

.NET Core
Кросс-платформенная, высокопроизводительная реализация .NET с открытым исходным кодом. Включает в себя Core Common Language Runtime (CoreCLR), Core AOT Runtime (CoreRT, находится в разработке), библиотеку базовых классов Core BCL и набор средств разработки Core SDK.
Документация .NET Core

.NET Core CLI (Интерфейс Командной Строки)
Кросс-платформенный набор инструментов для разработки приложений .NET Core.
Обзор .NET Core CLI

.NET Core SDK (Набор Средств Разработки)
Набор библиотек и инструментов, позволяющих разработчикам создавать приложения и библиотеки .NET Core. Включает .NET Core CLI для создания приложений, библиотеки .NET Core и среду выполнения для создания и запуска приложений, а также исполняемый файл dotnet (dotnet.exe), который выполняет команды CLI и запускает приложения.
Обзор .NET Core SDK

.NET Framework
Реализация .NET, работающая только в Windows. Включает общеязыковую среду выполнения (CLR), библиотеку базовых классов и библиотеки фреймворков приложений, такие как ASP.NET, Windows Forms и WPF.
Документация .NET Framework

.NET Native
Цепочка инструментов AOT-компиляции. Компиляция происходит на машине разработчика аналогично тому, как работают компилятор и компоновщик в C++. Компилятор удаляет неиспользуемый код и тратит больше времени на оптимизацию. Он извлекает код из библиотек и объединяет их в исполняемый файл. В результате получается один модуль, представляющий всё приложение.
UWP была первой платформой приложений, поддерживаемой .NET Native. Теперь поддерживается создание нативных консольных приложений для Windows, macOS и Linux.

.NET Standard
Формальная спецификация API .NET, доступных в каждой реализации .NET. Спецификацию .NET Standard в документации иногда называют библиотекой. Но поскольку библиотека включает в себя реализации API, а не только спецификации (интерфейсы), называть .NET Standard «библиотекой» неправильно.
Документация .NET Standard

Источник: https://docs.microsoft.com/en-us/dotnet/standard/glossary
День пятьсот пятьдесят шестой. #ЗаметкиНаПолях
Асинхронный Mетод без async в C#
Недавно я изменил сигнатуру метода действия контроллера следующим образом:
public async Task GetRecordAsync(int id) => await repo.GetAsync(id);
на
public Task GetRecordAsync(int id) => repo.GetAsync(id);

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

Итак, мы можем безопасно опустить async/await, поскольку вызывающий метод GetRecordAsync в конечном итоге возвращает то же, что и вызываемый, repo.GetAsync. Вместо того, чтобы наш метод ожидал завершения задачи и возвращал результат, он передаёт эту работу вызывающей стороне.

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

Подробнее про работу async/await

Удаление ключевых слов async/await предотвращает создание конечного автомата. Это немного снижает накладные расходы на выполнение метода. Хотя я не удивлюсь, если компилятор достаточно умён, чтобы оптимизировать этот момент.

Ловушки отсутствия async/await
1. Отсутствие методов в трассировке стека
Рассмотрим следующую цепочку методов:
static async Task Top() => await Middle();
static Task Middle() => Bottom();
static async Task Bottom() {
await Task.Delay(10);
throw new Exception("Bottom Exception");
}

При вызове Top(), трассировка стека не будет включать вызов Middle():
System.Exception: Bottom Exception
at ConsoleApp1.Bottom()
at ConsoleApp1.Top()
at ConsoleApp1.Program.Main(String[] args)

2. Отмена Заданий при Использовании using
Код ниже приводит к ошибке TaskCanceledException:
Task<string> Using HttpClient() {
using var client = new HttpClient();
return client.GetStringAsync("https://1.1.1.1");
}
Это происходит потому, что объект HttpClient удаляется до того, как завершается асинхронный запрос, тем самым отменяя все текущие запросы.

Итого
В общем случая я бы не рекомендовал удалять async/await. Как говорится, преждевременная оптимизация - это корень зла. Можно рассмотреть удаление ключевых слов, если:
- Асинхронный метод - это просто звено в цепочке вызовов.
- Метод находится на «горячем» участке кода.

Источник: https://drakelambert.dev/2020/07/Asynchronous-Method-Without-async-in-C%23