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

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

admin - @haarrp
加入频道
Числовые преобразования в 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
#post

.NET 6 is on the way, and I wanted to share some of my favorite new APIs in .NET and ASP.NET Core that you are going to love. Why are you going to love them? Well because they were directly driven by our fantastic .NET developer community! Let’s get started!

Read more...
#book

Beginning C# and .NET, 2021 Edition
Perkins Benjamin, Reid Jon D.

Get a running start to learning C# programming with this fun and easy-to-read guide.

As one of the most versatile and powerful programming languages around, you might think C# would be an intimidating language to learn. It doesn't have to be!

Download the book