C# 1001 notes
6.51K subscribers
329 photos
9 videos
2 files
313 links
Регулярные короткие заметки по C# и .NET.

Просто о сложном для каждого.

admin - @haarrp
加入频道
Статические классы в C#

Сегодня хотелось бы чуть подробнее остановиться на теме статических классов.

Класс считается статическим если в его сигнатуре присутствует ключевое слово static, например:

public static class Math { 
// ..
}


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

Более того, на статический класс также накладываются следующие ограничения:

🔸 не может учавствовать в наследовании (служить базовым или являться наследником);

🔸 не может содержать члены с атрибутами доступа protected и protected internal;

🔸 не может иметь переопределённых (override) членов;

🔸 не может содержать конструкторы экземпляров (только static);

Областей применения у статических классов не так много. Они служат для:

🔸 группировки вспомогательных методов (так например Math агрегирует в себе математические операции, Console содержит поля и методы для взаимодействия с консолью, Utils как контейнер для наиболее часто используемых методов вашего приложения);

🔸 определения методов расширения (extension methods).

Хочу также порекомендовать неплохую на мой взгляд русскоязычную заметку по теме - Статика в C#.

#basics
​​❇️ На этой неделе Microsoft поделилась деталями предстоящих обновлений .NET Core 3.0 и .NET Framework 4.8.

Ну а я, по традиции, предлагаю вашему внимаю самые 🔥 интересные статьи и вопросы этой недели:

🔸 C# Intermediate – Inheritance in C#

🔸 .NET Internals - Application execution model

🔸 Can a `Task` context switch before its first `await`?

🔸 What is Clean Code ?

🔸 The Introvert's Guide to Professional Development

🎧 Inversion of Control

Всем замечтательной погоды за окном и отличных выходных!

Понравилась заметка? Тогда поделись ей с другими 😉

#sof_weekly
Числовые преобразования в C#

Ранее я уже писал о числовых типах, которые существуют в языке C#. Сегодня же речь пойдёт об их преобразовании.

В тот момент, когда значение одного типа (допустим int) присваивается переменной другого типа (например long), выполняется преобразование типов.

Значения значимых типов преобразуются (конвертируются) неявно (implicitly) тогда и только тогда, когда отсутствует вероятность потери точности результата (data loss). Это возможно в том случае, когда тип, к которому мы хотим преобразовать наше значение, хранится в памяти в большем или равном количестве бит, как и исходный.

🔸 implicit преобразования:

int i = 12;
long l = i; // Implicit (int to long)
float f = i; // Implicit (int to float)
double d = 4.2f; // Implicit (float to double)


Как видно из кода выше, нам не требуется никаких дополнительных действий для преобразования, ведь long и double располагаются в 64 битах, в то время как int в 32, а значит потеря точности результата нам не грозит.

🔸 explicit преобразования:

long l = 12;
int i = l; // Compiler error


Если мы захотим выполнить аналогичное преобразование в обратном направлении, то компилятор не позволит нам это сделать и сообщит об ошибке на этапе компиляции, как в примере выше.

В таком случае мы будем вынуждены производить преобразования уже явно:

int i = (int)l;   // Explicit (long to int)
float f = 4.2f;
i = (int)f; // Explicit (float to int)
double d = 4.2f;
f = (float)d; // Explicit (double to float)


💬 Позвольте познакомить вас со своим тёзкой, который также имеет богатый практический опыт в разработке программного обеспечения и преподает айтишные предметы в ВУЗе. Почитайте https://yangx.top/tobeITmen, чтобы узнать, каково это - быть айтишником 😉

#data_types
​​Явное приведении значимых типов в C#

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

🔸 Целочисленное к целочисленному - урезание ведущих битов;

🔸 Decimal, float и double к целочисленному - обрезание дробной части и ведущих битов;

🔸 Double к float - округление или Infinity в случае переполнения;

🔸 Float и double к decimal - округление;

🔸 Decimal к float или double - потеря точности;

Также не стоит забывать и об операторе checked, который выбрасывает исключение в случае переполнения.

Подробнее с правилами явного приведения вы сможете ознакомиться уже в документации.

💬 На мой взгляд, необходимости заучивать все эти правила нет, однако, помнить - безусловно стоит. Если у вас ещё остались вопросы по преобразованиям или числам с плавающей точкой, то я настоятельно рекомендую вам ознакомиться со следующей темой самостоятельно - Difference between decimal, float and double in .NET?.

#data_types
Статические методы типа System.Char в C#

Тип char в .NET содержит большое количество статических методов, которые позволяют проверить и получить необходимую информацию о символе.

Далее я перечислю лишь некоторые из них:

🔸 char.GetNumericValue

Метод возвращает численное значение символа:

char.GetNumericValue('7');   // 7
char.GetNumericValue('¼'); // 0.25
char.GetNumericValue('Ⅸ'); // 9


🔸 char.GetUnicodeCategory

Метод возвращает UnicodeCategory, к которой относится символ:

char.GetUnicodeCategory('a');            
// LowercaseLetter

char.GetUnicodeCategory('2');
// DecimalDigitNumber

char.GetUnicodeCategory("Upper Case", 6);
// UppercaseLetter


🔸 char.IsControl

Метод возвращает признак того, является ли символ управляющим:

char.IsControl('a');    // false
char.IsControl('\t'); // true


🔸 char.IsDigit

Метод возвращает признак того, является ли символ цифрой:

char.IsDigit('a');      // false
char.IsDigit('¼'); // false
char.IsDigit('3'); // true


🔸 char.IsLetter

Метод возвращает признак того, является ли символ буквой:

char.IsLetter('%');     // false
char.IsLetter('P'); // true


🔸 char.IsLower

Метод возвращает признак того, что символ в нижнем регистре:

char.IsLower('j');      // true
char.IsLower('Y'); // false


🔸 char.IsNumber

Метод возвращает признак того, является ли символ числом:

char.IsNumber('a');     // false
char.IsNumber('¼'); // true


За скобками остались методы проверки символов на пунктуацию, приведения к определённому регистру и другие, найти которые вы сможете в документации к типу Char.

💬 Хотели бы больше информации и общения по C# и dotNET? На днях канал @devdigest опубликовал отличную, на мой взгляд, подборку тематических ресурсов, которой не премину поделиться и с вами.

#data_types
Литералы типа float и суффикс f в C#

Предполагаю, что тема числовых типов уже порядком вам поднадоела, однако, мне ещё есть чем с вами поделиться 🙂

Итак.. давайте проинициализируем несколько переменных типа float целочисленным и дробным значениями:

float f1 = 4;
float f2 = 4.2;


Есть ли на ваш взгляд здесь упущение?

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

Literal of type double cannot be implicitly converted to type 'float'.


Прежде чем я объясню почему так происходит, давайте разберём первую строку и вспомним правила приведения типов:

float f1 = 4;


В данном случае значение 4 является литералом типа int, который, в свою очередь, компилятор сумеет привести к типу float неявно (implicitly) ввиду отсутствия вероятности потери точности. Если этот момент вам не совсем понятен, я предлагаю вам вернуться к следующей заметке и вспомнить правила приведения типов.

Таким образом нам следует запомнить, что согласно спецификации языка C# тип любого целочисленного литерала определяется в зависимости от его значения (int, uint, long, ulong).

Отлично, надеюсь с этим разобрались. Так а что же со второй строкой? На самом деле- всё просто, и вот вторая вещь, которую нам стоит запомнить:

Любой дробный литерал в исходном коде является значением типа double, а значит мы пытаемся присвоить значение типа double переменной float.

Сделать это компилятор нам позволить не может, ввиду того, что такое приведение типов не может быть осуществлено неявно, а значит, мы должны явно (explicitly) дать понять, что нас это устраивает, следующим образом:

float f2 = (float)4.2;
float f3 = 4.2f;


В завершение мне осталось лишь отметить, что типы float и double являются двоичными типами с плавающей запятой, соответствующими стандарту IEEE 754, а значит значение 4.2 будет представлено как 4.19999980926513671875E0 для float (32 бита) и 4.20000000000000017763568394003E0 для double (64 бита).

💬 Если у вас после прочитанного ещё остались силы и интерес к данной теме, то я порекомендую вам замечательную статью Jon Skeet - Binary floating point and .NET и раздел Literals спецификации языка C# уже в качестве самостоятельного изучения 😉

#data_types
​​❇️ Рабочая неделя заканчивается, а значит пришло время очередной недельной подборки на выходные.

Предлагаю вашему внимаю самые 🔥 интересные статьи и вопросы этой недели:

C#:

🔸 Abstract Classes in C#

🔸 Generics in C#

🔸 Why would one ever use the “in” parameter modifier in C#?

🔸 Producer/Consumer with C# structs?

dotNET:

🔸 How to Get Started with SQL Server and .NET

🔸 Why I cannot create my own analogue of Nullable?

🔸 Looking inside the memory pool

🔸 What is the purpose of public static DateTime ToDateTime(DateTime value)?

Development:

🔸 Когда программный код вызывает восхищение?

🔸 Принципы SOLID, о которых должен знать каждый разработчик

Всем хороших выходных 😉

#sof_weekly
​​Инкремент и декремент в C#

Инкремент – это операция, которая увеличивает переменную на единицу, если переменная числовая, и возвращает следующий символ из таблицы символов, если переменная символьного типа (char).

Операторы инкремента записывается как два плюса: ++

Существуют два вида инкрементов: преинкремент (или префиксный инкремент) и постинкремент (или постфиксный инкремент). В синтаксисе префиксный инкремент ставится перед необходимой переменной, а постфиксный, соответственно, после.

Главное различие между ними, что при использовании операции преинкремента значение переменной сначала увеличивается на 1, а затем используется в выражении, к которому относится данная переменная:

int n1 = 5;
int n2 = 2 * ++n1; // n2 now 12, n1 is 6


А при использовании операции постинкремента значение переменной сначала используется в выражении, а потом увеличивается на 1:

int n1 = 5;
int n2 = 2 * n1++; // n2 now 10, n1 is 6


Декремент – это подобная инкременту операция, с той лишь разницей, что она уменьшает числовую переменную на единицу, а для символьной переменной выбирает предшествующий ей символ из таблицы символов.

Операторы декремента записывается как два минуса: --

Декремент также имеет два вида: предекремент (префиксный декремент) и постдекремент (постфиксный декремент).

int n1 = 5;
int n2 = 2 * n1--; // n2 now 10, n1 is 4


Вот небольшое задание для проверки.

💬 А знаете ли вы, что синий значёк говорит нам о том, что выполнение продолжилось уже в другом потоке? 😉

#basics
Арифметические операции в C#

Во вчерашней заметке мы рассмотрели унарные операции инкремента и декремента.

Сегодняшняя тема достаточно простая, однако, обойти её стороной, на мой взгляд, было бы неправильно. Итак.. бинарные арифметические операции в C#:

🔸 + - сложение двух чисел:

int x = 10;
int z = x + 12; // 22


🔸 - - вычитание двух чисел:

int x = 10;
int z = x - 6; // 4


🔸 * - умножение двух чисел:

int x = 10;
int z = x * 5; // 50


🔸 / - деление двух чисел:

int x = 10;
int z = x / 5; // 2

double a = 10;
double b = 3;
double c = a / b; // 3.33333333


При делении стоит учитывать, что если оба операнда представляют целые числа, то результат также будет округляться до целого числа:

double z = 10 / 4; // 2


Хочу обратить ваше внимание на то, что несмотря на тип переменной double, которой будет присвоено итоговое значение, результат деления будет целочисленным числом ввиду того, что литералы 10 и 4 имеют целочисленный тип int.

Для выхода из этой ситуации необходимо определять литералы или переменные, участвующие в операции, именно как типы double или float:

double z = 10.0 / 4.0; // 2.5


🔸 % - остаток от целочисленного деления:

double x = 10.0;
double z = x % 4.0; // 2


💬 Помните ли вы порядок выполнения операторов? Вот небольшое задание для проверки 😉

#basics
Целочисленное деление и округление в C#

При делении одного целочисленного значения на другое с помощью оператора деления / результат всегда округляется до нуля. Другими словами- обрезается:

int n1 = 7 / 2;       // 3
long n2 = -7 / 2; // -3
short n3 = -11 / -3; // 3


Причину этого поведения я описывал в предыдущей заметке - целочисленные аргументы приводят к целочисленному результату.

При попытке поделить на значение, равное 0, мы получим исключение System.DivideByZeroException в runtime:

int i = 0;
int r = 7 / i; // DivideByZeroException


При попытке поделить на литерал 0 мы получим исключение на этапе компиляции:

int r = 7 / 0; // Division by constant zero


💬 Тем удивительнее оказывается тот факт, что в случае деления числа с плавающей точкой на ноль (1.0 / 0) вышеупомянутое исключение выброшено не будет. Мы просто получим в результате бесконечность (Infinity) 🙂

#basics
Округление чисел с плавающей точкой в C#

Во время разработки мы временами сталкиваемся с необходимостью округлить число с плавающей точкой типа float или double к целочисленному значению типа int. Сделать это неявно, как я уже упоминал ранее, у нас не получится ввиду отсутствия реализации подобного приведения:

int n1 = 4.8f;  // Cannot implicitly convert


Поэтому в дело вступает явное приведение:

int n1 = (int)4.8f;


Однако, с этим кодом всё не так просто. Дело в том, что подобное округление на деле окажется ничем иным, как отбрасыванием дробной части у целочисленного значения.

Если же мы хотим руководствоваться математическими правилами округления, то с этим нам поможет класс System.Convert:

float f1 = 4.8f;
int n1 = Convert.ToInt32(4.8f); // 5


Но и здесь всё не всегда так гладко 😅 Оказывается, в .NET алгоритм округления (banker's rounding) отличается от привычного нам в тех случаях, когда значения являются пограничными: 0.5, 3.5. В этих случаях округление осуществляется в пользу ближайшего чётного:

int n1 = Convert.ToInt32(8.5f);  // 8
int n2 = Convert.ToInt32(9.5f); // 10


💬 Заинтересованы алгоритмом и причиной подобного решения в .NET? Подробнее почитать об этом вы сможете уже самостоятельно здесь 😉

#basics
​​❇️ Выходные на пороге, а значит время очередного еженедельного дайджеста.

Предлагаю вашему внимаю самые 🔥 интересные вопросы этой недели:

🔸 C# Intermediate – Queue, Stack, And Hashtable in C#

🔸 What does .NET's Equals method really mean?

🔸 Use structures to improve the readability of your code

🔸 Playing with C# 7 - Deconstruct

🔸 The Evolution of C#

🔸 IEnumerable<T> and .Where Linq method behaviour?

🔸 How to find all classes that implements a generic abstract class using reflection in C#?

🔸 Why are 1000 threads faster than a few?

Всем отличных выходных 😉

#sof_weekly
Переполнение в C#

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

Такая ситуация называется переполнением (overflow) и корнями уходит к арифметике и битовому представлению чисел в computer science.

По умолчанию в C# и .NET при переполнении ведущие биты обнуляются, после чего результат "умещается" в доступных битах. В случае с беззнаковыми типами - большие значения станут меньше, а при переполнении знаковых типов положительные числа станут отрицательными.

Позвольте мне продемонстрировать это поведение на примерах:

🔸 Максимальным значением для беззнакового типа uint является 0xffffffff:

uint u1 = 0xffffffff;
u1 = u1 + 5; // 0x00000004 (перенос)


🔸 int.MaxValue - максимальное значение для знакового типа int:

int n1 = int.MaxValue;
n1 = n1 + 1; // -2147483648 (перенос)


🔸 Минимальное значение для знакового типа short является -32768:

short s1 = short.MinValue;
s1 = (short)(s1 - 1); // 32767 (перенос)


💬 Как я уже упоминал ранее, использовать минимальные и максимальные значения в циклах стоит с осторожностью, иначе это может привести к неожиданным последствиям. Примером тому служит следующий код. Что выведется на экране? Ответ вы сможете найти ниже на странице 😉

#data_types
​​Контроль переполнения в C#

В прошлой заметке я уже упоминал о том, что по умолчанию при переполнении ведущие биты обнуляются, после чего результат "умещается" в доступных битах:

int n1 = int.MaxValue;
n1 = n1 + 1; // -2147483648 (перенос)


Однако это поведение может быть изменено и для этого мы можем воспользоваться ключевым словом checked:

int n1 = int.MaxValue;
n1 = checked(n1 + 1); // OverflowException


В таком случае вместо продолжения выполнения программы будет выброшено исключение OverflowException, которое мы можем или корректно обработать, или проигнорировать для завершения программы.

Синтаксис ключевого слова checked позволяет контролировать переполнение не только у конкретных операторов, но и целых блоков кода:

checked
{
int n1 = int.MaxValue;
n1 = n1 + 1; // OverflowException
}


Также стоит упомянуть о возможности изменения поведения при переполнении в настройках проекта: Project > Properties > Build > Advanced > Check for arithmetic overflow/underflow.

#data_types
Выключение контроля переполнения в C#

В предыдущей заметке я рассказывал о ключевом слове checked и возможности устанавливать поведение по умолчанию в случае переполнения в рамках проектов. Нелишним будет упомянуть и о ключевом слове unchecked, которое делает обратное.

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

int n1 = int.MaxValue;      // 2147483647 (0x7FFFFFFF)
int n2 = unchecked(n1 + 1); // Перенос -2147483648
int n3 = n1 + 1; // OverflowException


Как и в случае с checked мы так же может использовать unchecked не только для конкретных операторов, но и целых участков кода:

int n1 = int.MaxValue;  // 2147483647
unchecked
{
int n2 = n1 + 1; // Перенос -2147483648
int n4 = n1 * 2; // -2
}


Уверен, вы замечали, что следующий код не будет скомпилирован и отобразится ошибка:

int n1 = int.MaxValue + 1;  // Ошибка компиляции: overflow


В этом случае нам также может помочь unchecked:

int n2 = unchecked(int.MaxValue + 1);


💬 Одной из полезных возможностей в Visual Studio для повышения продуктивности для меня являются сниппеты (настоятельно рекомендую ознакомиться всем тем, кто их не использует 😉). А какой из них вы используете чаще всего?

#data_types
​​Parse чисел в C#

Каждый числовый тип в C# содержит метод Parse, с помощью которого мы можем преобразовывать строки в соответствующие числовые значения:

byte b1 = byte.Parse("200");
sbyte sb1 = sbyte.Parse("-100");
float f1 = float.Parse("1.2e-4");


Однако, важно отметить, что результат выполнения этого метода может обернуться для нас и следующими исключениями:

🔸 FormatException:

int n1 = int.Parse("3.4");    // FormatException


В этом примере мы пробуем привести (распарсить) дробное значение к типу int, в результате чего получаем исключение о некорректности формата входного параметра 3.4.

🔸 OverflowException:

uint ui1 = uint.Parse("-1");  // OverflowException


В данном случае мы пытаемся привести отрицательное число к типу uint, значения которого могут быть только положительными. Как итог - исключение, сигнализирующее о переполнении.

💬 Одним из полезных атрибутов, помогающем как в документации, так и в контроле поведения, является ObsoleteAttribute. С его помощью мы можем помечать элементы программы, которые больше не должны использоваться и вскоре могут быть удалены. Обычно мы получаем предупреждение, однако, знали ли вы, что это поведение настраиваемое и мы можем даже возвращать полноценную ошибку компиляции 🙂?

#strings
❇️ Рабочая неделя заканчивается, а значит пришло время очередной недельной подборки на выходные.

Предлагаю вашему внимаю самые 🔥 интересные статьи и вопросы этой недели:

🔸 Why does Enumerable.Single() iterate all elements, even when more than one item has already been found?

🔸 How to use Factory Method Design Pattern in C#

🔸 C# Intermediate – Delegates in C#

🔸 How to properly implement an interface that was designed for async usage?

🔸 Secure Random Integers in .NET Core 3

🔸 .NET Standard vs. .NET Core

Всем отличных выходных 😉

#sof_weekly
Конкатена́ция строк в C#

В C# мы можем использовать оператор + не только для сложения чисел, но и склеивания (конкатенации) строк:

string s1 = "C#";
string s2 = "fun";
string s3 = s1 + " is " + s2; // "C# is fun"


Мы можем использовать этот оператор неограниченное количество раз в рамках одного выражения (expression), а само выражение использовать в тех местах кода, где ожидается строка:

string s1 = "Hello " + " Wor" + "ld";
Console.WriteLine("Wish " + "you " + "the best");


Более того, специальные методы String.Concat и String.Format содержат дополнительные перегрузки, которые также могут быть использованы для конкатенации:

// Concat method
string s4 = String.Concat(new object[] {
"The ", 3, " musketeers"
});

string s5 = String.Concat("This", "That");

// Use String.Format to concatenate
string s6 = string.Format("{0}{1}{2}", s1, " is ", s2);


💬 Продолжая рассказывать про полезные фичи в C# нельзя не упомянуть coalesce оператор ??. Принцип его работы прост- возвращать left-hand операнд если он не null и right-hand в обратном случае: int y = x ?? -1. Берите на вооружение 😉

#strings
Forwarded from SeasonedDev
Dear friend,

If you read this, then you, like me, are clearly passionate about programming and tech 💻

My name is Maxim and I'm happy to meet you here ✌️

I hope that what I post on this channel will take your software development skills to the next level.

💬 If you have any questions, then you can always contact me via the @webdev_en chat.

See you at the new channel: @seasoneddev
What will be the output of the following program?
Anonymous Quiz
41%
Error occurred!
47%
Index error occurred!
12%
Compile-time error