Обложка канала

C# 1001 notes

Регулярные короткие заметки по C# и .NET. Просто о сложном для каждого.

  • C# 1001 notes

    Конкатена́ция строк в 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
  • C# 1001 notes

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

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

    🔸 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# 1001 notes

    ​​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
  • Реклама

  • C# 1001 notes

    Выключение контроля переполнения в 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
  • C# 1001 notes

    ​​Контроль переполнения в 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# 1001 notes

    Переполнение в 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# 1001 notes

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

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

    🔸 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# 1001 notes

    Округление чисел с плавающей точкой в 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# 1001 notes

    Целочисленное деление и округление в 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# 1001 notes

    Арифметические операции в 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# 1001 notes

    ​​Инкремент и декремент в 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# 1001 notes

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

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

    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# 1001 notes

    Литералы типа 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# 1001 notes

    Статические методы типа 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
  • C# 1001 notes

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

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

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

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

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

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

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

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

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

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

    #data_types
  • Реклама

  • C# 1001 notes

    Числовые преобразования в 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://t.me/tobeITmen, чтобы узнать, каково это - быть айтишником 😉

    #data_types
  • C# 1001 notes

    ​​❇️ На этой неделе 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# 1001 notes

    Статические классы в C#

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

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

    public static class Math { 
    // ..
    }


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

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

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

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

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

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

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

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

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

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

    #basics