.NET Разработчик
6.51K subscribers
422 photos
2 videos
14 files
2.03K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День тридцать третий. #ЗаметкиНаПолях
Использование структур
Думаю, всем знакомо понятие структуры (значимого типа) в C#, его особенности и отличия от класса. Но зачем использовать такой «урезанный» класс?
1) Структуры улучшают производительность при использовании небольших коллекций значимых типов, которые вы хотите сгруппировать вместе. Например, точка с координатами:
struct Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
ВАЖНО! Используйте структуру, если все её поля представляют собой значимые типы. Если хотя бы одно поле ссылочное (например, строковое), используйте класс.

2) В C# использование значимых типов вместо ссылочных приведёт к меньшему количеству обращений к куче, что снизит нагрузку на сборщик мусора, уменьшит количество циклов сборки мусора, и, соответственно, улучшит производительность.

3) При создании структуры переменная, которой она присваивается, содержит реальные данные. В отличие от экземпляра класса, который содержит только ссылку на объект в куче. Когда структура присваивается другой переменной, значения копируются. Таким образом, переменные будут содержать две различные копии одинаковых данных.

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

Кроме того, желательно, чтобы создаваемая вами структура отвечала следующим требованиям:
1. Логически представляла единичное значение
Структуры должны использоваться для представления единичного значения, поскольку они являются значимыми типами, как число. Не имеет смысла хранить экземпляр объекта для числа 5. Это важно, потому что структуры передаются по значению. То есть, когда вы передаёте структуру в параметр метода, всё содержимое структуры копируется, и все изменения структуры внутри метода не влияют на исходную переменную, если только вы не передаёте её явно по ссылке.
2. Имела размер экземпляра не больше 16 байт
Этот совет относится исключительно к производительности. 16 байт считается идеальным размером, поскольку структуры копируются по значению. Считается, что копирование 16 байт в стеке потока занимает примерно то же время, что и обращение в кучу по ссылке.
Этот совет не должен быть определяющим. Вы никогда не должны принимать проектные решения, основываясь на производительности, не проведя измерений. См. пост об оптимизации выше.
3. Была неизменяема
Этот совет основан на том, что структуры копируются по значению. Структуры выглядят, как классы, и вы не получите никакого предупреждения, если будете изменять структуру в методе, передавая её туда по значению. Это ведёт к ошибкам, которые легко сделать и сложно потом отследить, что усложняет сопровождение кода. Если ваши структуры неизменяемые, эта ошибка будет исключена.
4. Не подвержена частой упаковке
Избегайте методов или приведений типов, ведущих к упаковке ваших структур, поскольку это дорогостоящая операция с точки зрения производительности.

Ошибочные предположения о структурах:
1. Структуры всегда размещаются в стеке потока, а не в куче
Структуры на самом деле могут размещаться в куче. Куча и стек потока на самом деле только детали реализации языка C#. В спецификации языка ничто не гарантирует, где объект будет размещён.
2. Использование структур вместо классов исключительно из соображений производительности – это нано-оптимизация
Использование структур вместо классов ради производительности – не очень хорошая идея. Даже размер вашей структуры не имеет значения. Используйте структуры, когда это обосновано семантикой программы.

Источники:
https://samrueby.com/2016/09/05/when-should-you-use-a-struct-instead-of-a-class/
https://www.c-sharpcorner.com/article/what-is-structure-and-when-to-use-in-c-sharp/
👍3
День тридцать четвёртый. #CodeComplete

7. Обработка ошибок
Ситуации, в которых требуется обработка ошибок, возникают, когда значения данных
- выходят за границы,
- необычного характера,
- неверного типа,
- не определены,
- неполные,
- избыточные,
- несоответствующие,
и т.п.

Решения по обработке ошибок:
1. Что программа делает с неверными данными?
2. Нужно ли обрабатывать все возможные ошибки или только самые основные?
3. Какая часть (части) программы ответственны за это?
4. Какие механизмы языка использовать для обработки ошибок?
5. Должны ли все ошибки обрабатываться одинаково?
6. Должны ли все части программы обрабатывать ошибки одинаково?
7. Являются ли эти решения решениями уровня кодирования, проектирования, архитектуры?

Варианты обработки ошибок в списке ниже представлены для использования в системах от наиболее устойчивых до наиболее корректных. В устойчивых системах (пользовательских приложениях) можно выдать хоть какой-нибудь результат, даже неточный. Например, в видеоигре выдать цвет соседнего пикселя или в потоке некритичных данных выдать предыдущее значение. В корректных системах (где безопасность данных критична) важен только точный результат. В противном случае лучше завершить работу приложения, чем выдавать ошибочные данные. Например, машине МРТ лучше выдать ошибку, чем выдать неверный снимок.
1. Использовать нейтральное значение
- null, 0, пустую строку
2. Заменить следующим верным значением
- следующая запись в базе, следующие данные, прочитанные с датчика
3. Выдать тот же ответ, что и в предыдущий раз
- та же температура термометра, ближайшее максимальное/минимальное значение
4. Записать сообщение в лог
- полезно при отладке (стоит обратить внимание на защищённость логов)
5. Вернуть код ошибки
- решение на уровне архитектуры приложения
- некоторые модули могут просто передавать ошибку на верхние уровни и рассчитывать, что она будет обработана там
6. Вызвать объект/процедуру обработки ошибок
- в этом случае обработка ошибок централизована
- вся программа должна знать об этой централизованной возможности и быть связана с ней
7. Вывести сообщение об ошибке
- ошибки обрабатываются локально
- способ обработки отдельных ошибок оставлен на откуп низким уровням (там выбирается наиболее подходящий в конкретном случае)
- имеет потенциальную опасность распространения кода пользовательского интерфейса по всему приложению
8. Завершить работу
- наиболее корректный способ
- используется в приложениях, для которых критична безопасность

Важность обработки ошибок:
- влияет на всю систему приложения (в разных приложениях это 25-75% всего кода);
- влияет на производительность всего приложения;
- это решение на уровне архитектуры приложения. Вы бы не хотели, чтобы обработка ошибок в приложении осуществлялась случайным образом и разным способом в каждом случае.
День тридцать пятый. #Оффтоп #МоиИнструменты
Ещё одна утилитка, которой я, правда, не так часто пользуюсь, как хотелось бы, Action Outline. Программа позволяет делать разнообразные заметки, списки TODO, даже файлы в древовидной структуре. Их можно помечать, перетаскивать, копировать, вставлять, экспортировать в Word и HTML и т.д., и т.п. В общем, очень удобный хранитель мыслей.
День тридцать шестой. #ЗаметкиНаПолях
“Знай свои инструменты”
Чем быстрее мы пишем код, тем лучше. Поэтому собрал тут несколько горячих клавиш и советов по работе в Visual Studio, которые помогут в этом. Не все сочетания работают во всех версиях, тестировал в VS 2017.

Горячие клавиши Панели задач Windows (n – номер по порядку)
1. Запуск программы – Win+Shift+n.
2. Запуск/переключение между программами – Win+n.
3. Отобразить всплывающее меню – Win+Alt+n.
4. Запустить программу от администратора – Win+Shift+Ctrl+n.
5. Размещение окон Windows на экране – Win+<стрелки>.

Visual Studio
1. Глобальный поиск по меню, модулям, документам – Ctrl+Q.
– Для поиска в определённом месте можно использовать фильтр, начинающийся с @
2. Object Browser – Ctrl+Alt+J.
3. Окно Solution Explorer – Ctrl+;.
4. Окно Class View – Ctrl+Alt+C.
5. Окно Toolbox – Ctrl+Alt+X.
6. Окно Перейти к… (Navigate To) – Ctrl+,.
7. Все окна и вкладки можно перетаскивать за заголовок окна и прикреплять к разным границам экрана, а также вынести в отдельное от Visual Studio окно. При этом положение отдельного окна сохраняется после перезапуска.

Solution Explorer
1. Теперь отображает не только проекты и файлы решения, но и классы и члены классов в каждом файле.
2. Отобразить только этот элемент и его дочерние элементы – Правая кнопка мыши на элементе -> Scope to this.
3. Отобразить только этот элемент и его дочерние элементы в новом окне – Правая кнопка мыши на элементе -> New Solution Explorer Window.
4. Фильтры
– Отобразить только открытые файлы – Ctrl+[,O.
– Отобразить только файлы с изменениями (если подключена система контроля версий) – Ctrl+[,P.
5. Отслеживание активного окна редактора в Solution Explorer – Tools -> Options -> Projects and Solutions -> General -> Track Active Item in Solution Explorer.

Навигация
1. Упрощенная навигация (переход к предыдущему/следующему редактированному блоку) – Ctrl+- и Ctrl+Shift+-.
2. Перейти к открывающей/закрывающей скобке – Ctrl+].
3. Перейти к определению или подсмотреть определение – F12 или Alt+F12
4. Найти все ссылки на элемент – Shift+F12.
– Переход по найденным ссылкам – F8.
5. Иерархия вызовов элемента (Call Hierarchy) – Ctrl+K,T.
6. Окно просмотра определения элемента (Code Definition Window) – Ctrl+W,D.
7. Переключение между открытыми файлами – Ctrl+Alt+<стрелка вниз>.

Редактор кода
1. Размер шрифта – Ctrl+Shift+<, Ctrl+Shift+> или Ctrl+<колёсико мыши>.
2. Редактирование по колонкам – Alt+Shift+<стрелки вверх/вниз> или Alt+<выделение мышью>.
3. Найти в документе – Ctrl+F, расширенный поиск – Ctrl+Shift+F.
4. Переход по всем вхождениям элемента – Ctrl+Shift+<стрелки вверх/вниз>.
5. Вставка нескольких последних элементов из буфера обмена – Ctrl+Shift+V.
6. Перемещение строки кода – Alt+<стрелки вверх/вниз>.
7. Дублирование строки кода – Ctrl+D.
8. Сворачивание/разворачивание блоков кода:
- свернуть все методы – Ctrl+M,O
- развернуть все методы – Ctrl+M,P
- свернуть выделенный код в блок […] – Ctrl+M, Ctrl+H
- свернуть/развернуть предыдущий блок или весь код – Ctrl+M,L
- свернуть/развернуть текущий блок – Ctrl+M,M.

Написание кода
1. IntelliSence
- Подсказки: напишите начало элемента или аббревиатуру по заглавным буквам (например, System.AOORE для System.ArgumentOutOfRangeException)
- Переход в режим подсказок (без автозаполнения) – Ctrl+Alt+Space.
2. Сниппеты (стандартные блоки кода: if, for, using, try и т.п.)
- Вставить сниппет - Ctrl+K,X или <ключевое слово>, Tab, Tab.
- Окружить выделенный код сниппетом - Ctrl+K,S.
3. Комментарии
- однострочные комментарии на весь выделенный блок – Ctrl+K,C.
- убрать комментарии с выделенного блока – Ctrl+U,C.
4. Форматирование
- форматировать документ согласно заданным стандартам – Ctrl+K,D.
- форматировать выделенный текст согласно заданным стандартам – Ctrl+K,F.
5. Сортировка/удаление неиспользуемых пространств имен – Ctrl+R, Ctrl+G.
Отладка
1. Выполнить до курсора – Ctrl+F10
- Следующий шаг – F10
- Зайти в… – F11
- Выйти из… – Shift+F11.
2. Точки останова – F9
- В точках останова можно задавать условия останова и действия при достижении точки (например, запись в лог) – Alt+F9, C.

Рефакторинг
1. Советы по рефакторингу – Ctrl+. или Alt+Enter.
2. Инкапсулировать поле – Ctrl+R,E.
3. Извлечь интерфейс из класса – Ctrl+R,I.
4. Извлечь выделенный код в метод – Ctrl+R,M.
5. Удалить/реорганизовать параметры метода – Ctrl+R,V или Ctrl+R,O.
6. Переименовать элемент (во всём проекте) – Ctrl+R,R.

Источник: Курс ITVDN. Visual Studio 2013 Tips & Tricks.
День тридцать седьмой. #CodeComplete
8. Предвидение изменений

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

Источники изменений:
- Зависимость от оборудования
- Форматы файлов
- Входящие и выходящие данные
- Нестандартный функционал языка
- Глобальные переменные
- Бизнес-правила
- Последовательность обработки элементов
- Требования, исключённые из текущей версии
- Функционал, запланированный на следующую версию
- И МНОГОЕ ДРУГОЕ…

Основные правила:
1. Используйте абстрактные интерфейсы.
2. Используйте именованные константы вместо жёстко прописанных литералов.
3. Используйте стратегии позднего связывания.
4. Используйте табличные техники (поиск значения в таблице вместо многоуровневых условных операторов).
5. Используйте процедуры вместо копирования кода.
6. Используйте простые процедуры, решающие одну простую задачу.
7. Разделяйте несвязанные между собой операторы.
8. Разделяйте код, реализующий основную функциональность, и специализированный код.
День тридцать восьмой. #МоиИнструменты
Продолжаю описывать мои инструменты. На очереди утилиты, берегущие глаза.
EyeLeo – программа-тренажёр. Напоминает вам периодически отвлекаться от экрана. Каждые 10 минут появляется напоминание сделать какое-нибудь лёгкое упражнение для глаз на 8 секунд: помигать, зажмуриться, повращать глазами или посмотреть в окно. А каждые 50 минут программа напоминает сделать длинный перерыв: отойти от экрана, размяться, дать отдохнуть глазам. Если вам нужно закончить срочное дело, большой перерыв можно отложить на 3 минуты (я иногда откладываю по 5 раз по 3 минуты) или отменить совсем. Все интервалы можно настроить. Например, я сделал большой перерыв не 5, а 10 минут.
А программа f.lux управляет настройками цветов вашего монитора. Вы сообщаете программе ваше местоположение (город), а программа в зависимости от вашего положения и даты подбирает время восхода и захода солнца и соответственно этому изменяет цветовую гамму экрана. Днём она настраивает более холодные (синие) тона, а вечером – более тёплые (красные). Очень помогает тем, что ночью экран не режет глаза. Переход с дневного режима на ночной во время сумерек плавный и длится целый час, поэтому происходит незаметно. В программе куча преднастроенных цветовых схем под разные вкусы, но и их можно изменять.
День тридцать девятый. #TipsAndTricks
Открываю серию постов формата Tips & Tricks (Советы и хитрости) по языку C#. Здесь не будет никакого порядка или логики. Просто всё, что буду находить, и что покажется интересным. Что-то может быть совсем тривиальным, а что-то «очень сильным колдунством».

1. Не забивайте код временными коллекциями, используйте вместо этого Yield
Временный список, содержащий отобранные возвращаемые значения, может создаваться, когда разработчики хотят выбрать значения из коллекции по некоторому условию. Например, рассмотрим следующий код:
public List<int> GetValuesGreaterThan100(List<int> masterCollection)
{
List<int> tempResult = new List<int>();
foreach(var value in masterCollection)
if (value > 100)
tempResult.Add(value);
return tempResult;
}

Чтобы избежать создания временной коллекции, можно использовать ключевое слово yield. Например, код выше можно переписать следующим образом:
public IEnumerable<int> GetValuesGreaterThan100(List<int> masterCollection)
{
foreach (var value in masterCollection)
if (value > 100)
yield return value;
}

Однако для простых случаев, вроде приведённого выше, можно использовать LINQ:
filteredCollection = masterCollection.Where(x => x > 100);
День сороковой. #CodeComplete
Командная работа

Важно:
1. По статистике код в среднем «переживает» до 10 поколений поддерживающих его программистов.
2. Программисты, поддерживающие код, проводят 50-60% своего времени, читая код.

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

Помимо собственно грамотно написанного кода, его пониманию способствуют комментарии.

Виды комментариев:
1. Повторяющие код. Такие комментарии не нужны, их необходимо убрать.
// добавляем 1 к i
i = i + 1;
2. Объясняющие код.
// i обозначает номер клиента
i = i + 1;

Замените i на какое-нибудь значимое имя, например, customer, и уберите такие комментарии.
3. Отмечающие место в коде.
return null; // *** НЕ СДЕЛАНО!!! ***

Замените такой комментарий на стандартный блок TODO.
4. Комментарии исправлений. Переместите такие комментарии в систему контроля версий.
for i = 1 to maxElements-1 //исправлена ошибка №1234
5. Кратко объясняющие суть блока кода. Важно поддерживать актуальность таких комментариев!
6. Описывающие предназначение кода. Используйте комментарии, близкие к реальному миру, а не к техническим подробностям реализации, например:
// получаем информацию о работнике
вместо
// обновляем объект employeeRecord
7. Содержащие информацию, которая не может быть представлена в коде. Например, авторские права, Javadoc, ссылки на онлайн ресурсы и т.п.
8. Содержащие краткий обзор или вводную информацию для модуля или библиотеки.
День сорок первый. #ЗаметкиНаПолях
Nullable-типы
Иногда бывают ситуации, когда необходимо чтобы тип значений мог принимать null, и это можно сделать, указав знак вопроса ? после имени типа, при объявлении переменной:
int? a = 5;
double? b = null;
bool? c = false;
Nullable-типы могут пригодиться при работе с базой данных. Некоторые поля таблицы базы данных могут принимать значение null.
Запись ? является упрощенной формой использования структуры System.Nullable<T>:
[System.Serializable]
public struct Nullable<T> where T : struct
Следующие виды определения переменных будут эквивалентны предыдущим:
Nullable<int> a = 5;
Nullable<double> b = null;
Nullable<bool> c = false;

Операторы
Любые операторы, которые определены для значимых типов, также могут быть использованы для nullable-типов. Эти операторы вернут null, если один или оба операнда null. В противном случае операторы используют содержащиеся значения для расчёта результата. Например:
int? a = 10;
int? b = null;
int? c = 10;
a++; // a = 11
a = a * c; // a = 110
a = a + b; // a = null
Для операторов сравнения (<, >, <=, >=), если один или оба операнда null, результатом будет ложь. Не рассчитывайте на то, что, если определённое сравнение (например, <=) возвращает ложь, то обратное сравнение (>) вернёт правду! Например, 10:
- не больше и не равно null,
- также и не меньше null.

Оператор ?? (null-объединение)
Оператор null-объединения ?? похож на тернарный оператор. Он имеет следующую структуру:
[операнд1] ?? [операнд2];
??
возвращает операнд1, если тот не равен значению null, иначе возвращает операнд2.
int? a = 1;
int? b = null;
Console.WriteLine(a ?? 3); // 1
Console.WriteLine(b ?? 3); // 3
Левый операнд, который сравнивается со значением null, обязательно должен быть nullable-типа, иначе возникнет ошибка компиляции.

Источники:
-
http://mycsharp.ru/post/47/2014_09_30_znachenie_null_nullable-tipy_operator___.html
-
https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/nullable-types/using-nullable-types
👍2
День сорок второй. #TipsAndTricks
2. Сохраняйте полный стек вызовов при выбросе исключения
Если в блоке catch программы C# выбрасывается исключение при помощи конструкции
catch (Exception ex)
{
throw ex;
}
то стек вызовов очищается, и верхним уровнем стека становится метод, выбросивший это исключение. Это значит, что настоящий источник исключения будет потерян, как показано в примере ниже:
static void Main(string[] args)
{
try
{
Method2();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.Write(ex.StackTrace.ToString());
Console.ReadKey();
}
}

private static void Method2()
{
try
{
Method1();
}
catch (Exception ex)
{
//throw ex очищает стек вызовов, идущий из Method1
// и добавляет в него только собственную запись
// перед передачей вызывающему методу (Main)
throw ex;
}
}

private static void Method1()
{
try
{
throw new Exception("Исключение внутри Method1");
}
catch (Exception)
{
throw;
}
}

Код выводит:
Исключение внутри Method1
at Program.Method2() in …\ConsoleApp1\Program.cs:line 30
at Program.Main(String[] args) in …\ConsoleApp1\Program.cs:line 9

Чтобы сохранить полный стек вызовов, используйте throw; без аргументов. При этом вы можете обработать переменную ex в блоке catch. Например, сделать запись в лог:
catch (Exception ex)
{
Console.WriteLine("Лог в Method2: {0}", ex.Message);
throw;
}

Такой код выведет:
Лог в Method2: Исключение внутри Method1
Исключение внутри Method1
at Program.Method1() in …\ConsoleApp1\Program.cs:line 39
at Program.Method2() in …\ConsoleApp1\Program.cs:line 23
at Program.Main(String[] args) in …\ConsoleApp1\Program.cs:line 9

Источник: https://stackoverflow.com/questions/730250/is-there-a-difference-between-throw-and-throw-ex
День сорок третий. #ЗаметкиНаПолях
Битовые флаги
Тип перечисления можно использовать для определения битовых флагов, благодаря чему экземпляр типа перечисления может хранить любую комбинацию значений, определенных в списке перечислителя.
Чтобы создать перечисление битовых флагов, нужно:
1. Добавить атрибут System.FlagsAttribute.
2. Определить значения так, чтобы для них могли выполняться битовые операции AND, OR, NOT и XOR.
3. Добавить именованную константу с нулевым значением, что означает "флаги не установлены" (не задавайте флагу нулевое значение, если оно не означает "флаги не установлены").
В следующем примере у перечисления Days имеется атрибут Flags, и каждому значению присваивается следующая степень числа 2:
[Flags]
enum Days
{
None = 0x0,
Sunday = 0x1,
Monday = 0x2,
Tuesday = 0x4,
Wednesday = 0x8,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}
Чтобы установить несколько флагов перечисления, используйте битовый оператор OR:
// Устанавливаем два флага с помощью битового OR
meetingDays = Days.Tuesday | Days.Thursday;

// Добавляем флаг с помощью битового OR
meetingDays = meetingDays | Days.Friday;

Console.WriteLine("Дни встречи: {0}", meetingDays);
// Вывод: Дни встречи: Tuesday, Thursday, Friday

// Снимаем флаг с помощью битового XOR
meetingDays = meetingDays ^ Days.Tuesday;
Console.WriteLine("Дни встречи: {0}", meetingDays);
// Вывод: Дни встречи: Thursday, Friday

Чтобы определить, установлен ли конкретный флаг, используйте битовый оператор AND:
// Проверка, установлен ли флаг с помощью битового AND
bool test = (meetingDays & Days.Thursday) == Days.Thursday;
Console.WriteLine("Thursday – это {0}день встречи.", test == true ? "" : "не ");
// Вывод: Thursday – это день встречи.

Кроме того, можно получить числовое значение строки, содержащей разделённые запятыми идентификаторы флагов, с помощью методов Parse или TryParse типа Enum:
Days d = (Days)Enum.Parse(typeof(Days), "monday,sunday", true);
Console.WriteLine("Дни встречи: {0}", d);
// Вывод: Дни встречи: Sunday, Monday

if (Enum.TryParse("friday,sunday", true, out d))
Console.WriteLine("Дни встречи: {0}", d);
// Вывод: Дни встречи: Sunday, Monday
При этом:
- значения могут быть неупорядоченными, метод Parse упорядочивает их;
- параметр ignoreCase позволяет игнорировать регистр символов;
- метод TryParse на самом деле обобщённый (TryParse<Days>), но параметр типа можно не указывать, он определяется по типу последнего out параметра.

Также для парсинга можно использовать числовое значение (сумму числовых значений флагов):
if (Enum.TryParse("108", true, out d))
Console.WriteLine("Дни встречи: {0}", d);
// Вывод: Дни встречи: Tuesday, Wednesday, Friday, Saturday

Источники:
- Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 15.
-
https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/enumeration-types
👍3
День сорок четвёртый. #TipsAndTricks
3. Оператор безопасной навигации
Вас ведь тоже бесит постоянно возникающее NullReferenceException, когда происходит обращение к неинициализированному объекту? Чаще всего null-исключение возникает из-за примерно следующего кода:
string name = null;
Console.WriteLine(name);

В большинстве случаев компилятор не позволит вам продолжить, пока вы не исправите код. Но если вы каким-то образом заставите компилятор думать, что переменная имеет значение, а во время выполнения его не окажется, будет выброшено NullReferenceException. Чтобы избежать этого, вы можете сделать следующее:
string name = null;
// Некоторый код
if(name != null) {
Console.WriteLine(name);
}

Это безопасная проверка, которая позволяет убедиться, что значение переменной доступно при её вызове.
Однако в C#6 появился другой способ обойти эту ошибку. Допустим, у вас есть база данных людей, и вы хотите получить данные о трудоустройстве человека, но такие данные отсутствуют, а вы хотите получить название компании-работодателя:
var company = DbHelper.PeopleTable.Find(x => x.id == id).FirstOrDefault().EmploymentHistory.CompanyName;

Если вы попытаетесь это сделать, возникнет ошибка. Как только в цепочке вызовов встречается null, всё пропало. В C#6 появился способ обойти это, используя оператор безопасной навигации после значений или полей, которые могут быть null. Вот так:
var company = DbHelper?.PeopleTable?.Find(x => x.id == id)?.FirstOrDefault()?.EmploymentHistory?.CompanyName;


Этот код будет переходить к следующему элементу в цепочке, только если предыдущий не null. В противном случае вся цепочка вернёт null вместо выброса исключения. Это может быть удобно оставить проверки фреймворку, однако, несмотря на это, вам нужно будет проверить итоговый результат на null.
if(company != null) {
// Дальнейшая обработка
}

Примечание! Стоит отметить, что при всём удобстве этого оператора есть соблазн спрятать плохое проектирование с его помощью, как в последнем примере. Всё-таки несмотря на то, что код не будет выдавать ошибок, связанных с неинициализированными объектами, он может скрыть ошибки, которые вы хотели бы обнаружить и исправить (например, неинициализированный объект DbHelper).

Источник:
https://www.pluralsight.com/guides/tips-for-writing-better-c-code
День сорок пятый. #TipsAndTricks
4. Добавление методов к перечислениям
Определить метод как часть перечислимого типа напрямую невозможно. Однако это ограничение можно обойти при помощи методов расширения. Рассмотрим упоминавшееся ранее перечисление дней недели (см. день сорок третий):
[Flags]
public enum Days
{
None = 0x0,
Sunday = 0x1,
Monday = 0x2,
Tuesday = 0x4,
Wednesday = 0x8,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}
Для добавления методов к перечислению нужно определить статический класс с методами расширения:
static class DaysExtensions
{
static Days GetWorkdays(this Days days)
{
// 62 – сумма значений элементов от Monday до Friday
Enum.TryParse("62", true, out days);
return days;
}

public static Days GetWeekends(this Days days)
{
// 65 – сумма значений элементов Sunday и Saturday
Enum.TryParse("65", true, out days);
return days;
}
}
Теперь эти методы можно использовать, вызывая их из экземпляра перечисления:
var d = Days.None;
Console.WriteLine("Рабочие дни: {0}", d.GetWorkdays());
Console.WriteLine("Выходные: {0}", d.GetWeekends());

// Вывод:
// Рабочие дни: Monday, Tuesday, Wednesday, Thursday, Friday
// Выходные: Sunday, Saturday
День сорок шестой. #ЗаметкиНаПолях
Кортежи
C# предоставляет богатый синтаксис классов и структур, который можно использовать для различных целей проектирования. Но иногда даже такой богатый синтаксис требует дополнительной работы, которая не приносит особой пользы. Вам может потребоваться написать метод, который использует простую структуру, содержащую более одного элемента данных. Для поддержки таких сценариев в язык C# были добавлены кортежи. Кортежи – это простые структуры данных, содержащие несколько полей. Поля не проверяются, и вы не можете определить ваши методы для кортежа.
До С# 7 кортежи были отдельным классом, и работать с ними было не так удобно. В 7й версии языка появился упрощённый синтаксис. Вы можете создать кортеж, присвоив значение каждому его члену:
var letters = ("a", "b");
Обращаться к полям кортежа можно по именам Item1, Item2 и т.д. по порядку. Также им можно задать имена. Для этого есть два способа.
- Справа от оператора присваивания:
var alphabetStart = (Alpha: "a", Beta: "b");
- И слева от него:
(string Alpha, string Beta) namedLetters = ("a", "b");
Задание имён и справа, и слева приведёт к предупреждению компилятора, и имена справа будут проигнорированы.

Кортежи предоставляют упрощённый синтаксис для методов, возвращающих несколько дискретных значений. Вам не нужно создавать класс или структуру, определяющую возвращаемый тип. Создание кортежа более эффективно и более продуктивно. Пример ниже показывает метод, возвращающий максимальное и минимальное значения из последовательности целых чисел:
private static (int Max, int Min) Range(IEnumerable<int> numbers)
{
int min = int.MaxValue;
int max = int.MinValue;
foreach(var n in numbers)
{
min = (n < min) ? n : min;
max = (n > max) ? n : max;
}
return (max, min);
}
Объявление этого метода предоставляет имена полей возвращаемого кортежа. При вызове метода возвращаемым типом является кортеж, имена полей которого Max и Min:
var range = Range(numbers);
Console.WriteLine(range.Min);
Могут быть ситуации, когда вам нужно распаковать элементы кортежа, возвращённого из метода. Вы можете сделать это, объявив отдельные переменные для каждого из элементов кортежа. Это называется деконструкцией (разбором) кортежа:
(int max, int min) = Range(numbers);
Console.WriteLine($"{min} - {max}");

PS: использовать новые функции кортежей можно и в более ранних версиях языка, подключив пакет System.ValueTuple.

Источник: https://docs.microsoft.com/ru-ru/dotnet/csharp/whats-new/csharp-7#tuples
👍1
День сорок седьмой. #ЗаметкиНаПолях
Приведение типов или Convert.To()
Несмотря на то, что эти операции могут казаться эквивалентными, они совершенно разные по целям.
Приведение типа – это действие по изменению объекта из одного типа данных в другой. Это достаточно общее определение, и в некотором смысле оно похоже на конвертацию, поскольку приведение типа часто имеет тот же синтаксис, что и конвертация. Поэтому возникает вопрос, когда достаточно приведения типа (неявного или явного), а когда вам придётся использовать более явную конвертацию.


Формально приведение типа изменит тип, тогда как конвертация изменит (или может изменить) значение (вместе с типом). Также приведение типа обратимо, тогда как конвертация может быть необратимой.

Неявные приведения типов
В C# приведение типа происходит неявно, когда вы не теряете информацию (заметьте, что эта проверка происходит на уровне типов, а не на уровне реальных значений переменных). Например, для примитивных типов:
int tinyInteger = 10;
long bigInteger = tinyInteger;
float tinyReal = 10.0f;
double bigReal = tinyReal;

Эти приведения неявны, поскольку при этом вы не теряете информацию (вы расширяете тип). В обратную сторону неявное приведение невозможно, потому что вне зависимости от реальных значений (они проверяются только во время выполнения) при приведении вы можете потерять часть данных. Например, следующий код не скомпилируется, потому что тип double может содержать значения, не представленные в типе float:
double bigReal = Double.MaxValue;
float tinyReal = bigReal;

Для объектов приведение типов всегда неявно, когда компилятор уверен, что исходный тип – производный класс от целевого или реализует целевой интерфейс, например:
string text = "123";
IFormattable formattable = text;
NotSupportedException derivedException = new NotSupportedException();
Exception baseException = derivedException;

В этих случаях информация не теряется, поскольку объекты не меняют своих типов, меняется только то, в каком типе вы их представляете.

Явные приведения типов
Приведение типа явно, когда оно не может быть выполнено неявно компилятором, и вам нужно использовать оператор приведения типа. Обычно это значит, что:
1. Вы можете потерять информацию или данные.
2. Преобразование может завершиться неудачей (поскольку вы не можете привести один тип к другому).
Явное преобразование подразумевает, что вы понимаете эти угрозы.
Для примитивных типов:
double precise = Math.Cos(Math.PI * 1.23456) / Math.Sin(1.23456);
float coarse = (float)precise;
float epsilon = (float)Double.Epsilon;

В обоих примерах, несмотря на то, что значения входят в диапазон типа float, вы теряете информацию (точность), поэтому преобразование должно быть явным. А такое приведение завершится неудачей:
float max = (float)Double.MaxValue;

А следующее преобразование не скомпилируется, поскольку компилятор не умеет преобразовывать текст в число. Текст может содержать любые символы, не только цифры, поэтому в C# это недопустимо (хотя может быть допустимо в других языках):
string text = "123";
double value = (double)text;

Преобразования объектов могут завершиться неудачей, если типы несвязанные. Например, код ниже не скопмилируется:
string text = (string)AppDomain.Current;
Exception exception = (Exception)"abc";

Следующий код скомпилируется, но он может привести к ошибке времени выполнения InvalidCastException в зависимости от реального типа объекта:
object obj = GetNextObjectFromInput();
string text = (string)obj;
Exception exception = (Exception)obj;

Преобразования
Поэтому, отбрасывая некоторые незначительные нюансы реализации типа Convert или интерфейса IConvertible, в случае приведения типа в C# вы говорите компилятору:
«Доверься мне, этот тип можно привести к этому типу. Даже если ты этого не знаешь сейчас, позволь мне это сделать и всё получится.»
или
«Не беспокойся, мне неважно, если что-то потеряется при приведении.»
Для любого другого приведения, когда требуется более явная операция, требуется преобразование значения. Например, для приведения из string в double может потребоваться парсинг. Преобразование в строку, например, всегда возможно, (через метод ToString()), хотя может означать что-то другое, нежели то, что вы ожидаете.

Такие преобразования могут выполняться изнутри объекта, используя методы преобразования, либо с помощью более сложных механизмов (TypeConverter). Вы не знаете, что может случиться во время преобразования, поэтому, по моему мнению, когда более строгое преобразование доступно, вы должны его использовать.

Подводя итог, когда можно использовать явное приведение типов:
1. Приведение между базовыми типами.
2. Приведение от объекта в другой тип (может включать распаковку).
3. Приведение от производного в базовый класс (или в реализованный интерфейс).
4. Преобразование из одного типа к другому через специальные операторы преобразования.
Только первый из этих вариантов может быть реализован через тип Convert. Для остальных случаев у вас нет другого выбора, кроме явного приведения типа.

Когда можно использовать тип Convert:
1. Преобразование из одного базового типа в другой (с некоторыми ограничениями).
2. Преобразование из типа, реализующего интерфейс IConvertible в любой другой (поддерживаемый) тип.
3. Преобразования массива битов в строку и наоборот.

Заключение
По моему мнению, Convert должен использоваться во всех случаях, когда вы знаете, что преобразование может завершиться неудачей, даже если оно может быть выполнено с помощью приведения типов. Это даёт понять читающим ваш код ваши намерения и что вы знаете, что преобразование может завершиться неудачей. Для остальных случаев используйте приведение типа, т.к. нет выбора. Но если доступен другой метод (Parse, TryParse), я советую его использовать.

Кроме того, имейте в виду, что приведение типов и Convert иногда приводят к разным результатам. Например:
double real = 1.6;
int castedInteger = (int)real; // 1
int convertedInteger = Convert.ToInt32(real); // 2

Приведение типа (ожидаемо) отсекает дробную часть, а Convert осуществляет округление до ближайшего целого (а это может стать неожиданностью). Каждое преобразование может отличаться от приведения типа, поэтому единого правила для всех случаев нет. Лучше для каждого случая проконсультироваться с MSDN.

Источник: https://stackoverflow.com/questions/15394032/difference-between-casting-and-using-the-convert-to-method
👍1
День сорок восьмой. #TipsAndTricks
5. IntelliCode для Visual Studio
IntelliCode сохраняет ваше время, выбирая в качестве подсказки для автозаполнения то, что вы скорее всего будете использовать. Рекомендации IntelliCode основаны на тысячах проектов с открытым кодом на GitHub. При сочетании с контекстом вашего кода список подсказок теперь нацелен на наиболее используемые варианты. В результате вместо того, чтобы искать и прокручивать отсортированный список, вы получаете предложения наиболее вероятных вариантов в самом начале, как только начинаете печатать. При вводе члена класса вы заметите рекомендации IntelliCode, отмеченные звёздочкой (см. рисунок). Скачайте IntelliCode для VisualStudio здесь: https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.VSIntelliCode

Источник: https://devblogs.microsoft.com/visualstudio/argument-completion-made-easy-with-visual-studio-intellicode/
День сорок девятый. #ЗаметкиНаПолях
Деконструкция
Начиная с C# 7.0, вы можете извлекать несколько элементов из кортежа или несколько значений полей, свойств из объекта в одной операции деконструкции. При этом вы присваиваете значения полей кортежа или членов объекта отдельным переменным.
Деконструкция кортежа
Синтаксис деконструкции кортежа похож на синтаксис его определения (см. день сорок шестой). Допустим, есть метод QueryCityData, возвращающий для заданного города название, численность населения и площадь города.
Есть три варианта деконструкции:
1. Явно определить типы переменных
(string city, int population, double area) = QueryCityData("New York City");
2. Использовать ключевое слово var вне скобок для всех переменных:
var (city, population, area) = QueryCityData("New York City");
3. Использовать var любой или всех переменных:
(string city, var population, var area) = QueryCityData("New York City");
Этот метод использовать не рекомендуется.
Кроме этого, вы можете использовать уже ранее объявленные переменные:
string city = "Raleigh";
int population = 458880;
double area = 144.8;
(city, population, area) = QueryCityData("New York City");
Заметьте:
- вы не можете объявить определённый тип вне скобок, даже если все поля кортежа одного типа;
- вы обязаны присвоить каждое поле массива переменной;
- вы не можете смешивать объявления переменных с использованием ранее объявленных переменных.
Если значения каких-либо полей кортежа вам не нужны, вы можете использовать специальный символ пустой переменной "_". Например, если из примера выше нам нужно получить только население города:
var (_, population, _) = QueryCityData("New York City");

Деконструкция пользовательских типов
Некортежные типы не предлагают встроенной поддержки деконструкции. Но автор класса, структуры или интерфейса может позволить производить деконструкцию экземпляров класса, реализовав метод Deconstruct. Метод должен возвращать void, а каждое значение для деконструкции должно быть обозначено ключевым словом out. Например, следующий метод Deconstruct класса Person возвращает имя, отчество и фамилию:
public void Deconstruct(out string fname, out string mname, out string lname)
Произвести деконструкцию экземпляра класса Person, присвоенного переменной p, можно следующим образом:
var (fName, mName, lName) = p;
Можно перегружать методы Deconstruct для вызова разного количества и разных комбинаций свойств. Однако, нужно быть осторожным, определяя методы Deconstruct, с уникальными и однозначными сигнатурами. Несколько методов Deconstruct, имеющие одинаковое количество параметров, но разные типы или разный порядок параметров допустимы, но могут приводить к путанице.
При деконструкции экземпляров, так же, как и для кортежей, можно использовать пустые переменные.
Если вы не являетесь автором класса, вы можете производить деконструкцию экземпляров этого класса, реализовав один или несколько методов Deconstruct в качестве методов расширения.

Источник: https://docs.microsoft.com/ru-ru/dotnet/csharp/deconstruct
👍1