Изучение C# и написание консольных приложений

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

Начнем с изучения базовых элементов синтаксиса и лексики языка C#. Здесь вы создадите несколько консольных приложений, каждое из которых демонстрирует определенную особенность языка C#. Мы начнем с создания консольного приложения, которое показывает версию компилятора.

Содержание

Создание консольного приложения

  1. Создание папок для проекта:
    • Если вы завершили изучение прошлой статьи, то у вас уже должна быть папка Code в вашей пользовательской папке. Если нет, создайте ее.
    • Внутри папки Code создайте подпапку Chapter02, а внутри нее подпапку Basics.
  2. Запуск Visual Studio Code:
    • Откройте Visual Studio Code и откройте папку Chapter02/Basics.
  3. Создание нового консольного приложения:
    • В Visual Studio Code перейдите в меню View | Terminal и введите следующую команду:
      dotnet new console
      
    • Эта команда создаст новый проект консольного приложения в текущей папке.
  4. Настройка проекта:
    • В EXPLORER найдите файл Program.cs и кликните по нему. Visual Studio Code предложит добавить недостающие необходимые файлы, нажмите Yes.
  5. Редактирование файла Program.cs:
    • Откройте файл Program.cs и в верхней части файла, под директивами using, добавьте следующую строку для отображения текущей версии C# в виде ошибки:
      #error version
      
  6. Просмотр версии компилятора:
    • Перейдите в меню View | Problems и обратите внимание на сообщение об ошибке компилятора с номером CS8304, которое покажет версию компилятора и языка.Окно проблем VSCode
  7. Закомментируйте строку, вызывающую ошибку:
    • Чтобы закомментировать строку, которая вызывает ошибку, добавьте // перед ней, как показано ниже:
      // #error version
      

Что дальше?

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

Полезные советы

  • Работайте постепенно: Не торопитесь и старайтесь понять каждый шаг. Это поможет вам лучше усвоить материал.
  • Используйте документацию: Если возникают вопросы, не забывайте обращаться к официальной документации Microsoft.
  • Экспериментируйте: Попробуйте изменить код, добавлять новые строки и видеть, как это влияет на работу программы. Это поможет вам лучше понять, как работает C#.

Синтаксис C#

Грамматика C# включает операторы и блоки кода. Для документирования кода можно использовать комментарии.

Практический совет:

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

Операторы

В естественном языке предложение заканчивается точкой. Предложение может состоять из нескольких слов и фраз, причем порядок слов имеет значение. Например, в английском мы говорим «the black cat», где прилагательное «black» идет перед существительным «cat». В французском же порядке слов другой: «le chat noir».

В C# оператор заканчивается точкой с запятой. Оператор может состоять из нескольких переменных и выражений. Например, в следующем операторе totalPrice — это переменная, а subtotal + salesTax — выражение:

var totalPrice = subtotal + salesTax;

ыражение состоит из операнда subtotal, оператора + и еще одного операнда salesTax. Порядок операндов и операторов имеет значение.

Комментарии

При написании кода можно добавлять комментарии, чтобы объяснить его, используя двойной слэш //. Компилятор игнорирует все после // до конца строки, как показано в следующем примере:

// налог с продаж должен быть добавлен к промежуточной сумме
var totalPrice = subtotal + salesTax;

Visual Studio Code позволяет добавлять или удалять комментарии в начале выбранных строк с помощью сочетания клавиш Ctrl + K + C для добавления и Ctrl + K + U для удаления комментариев. В macOS вместо Ctrl используйте Cmd.

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

/*
Это многострочный
комментарий.
*/

Блоки

В естественном языке новый абзац обозначается началом новой строки. В C# блок кода указывается с помощью фигурных скобок { }. Блоки начинаются с объявления, указывающего, что определяется. Например, блок может определять пространство имен, класс, метод или оператор, что мы рассмотрим позже.

В вашем текущем проекте грамматика C# генерируется инструментом dotnet CLI. В проекте по шаблону добавлены комментарии к операторам, как показано в следующем примере:

using System; // точка с запятой указывает на конец оператора

namespace Basics
{ // открывающая фигурная скобка указывает на начало блока
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!"); // оператор
        }
    }
} // закрывающая фигурная скобка указывает на конец блока

Заключение

Эти основы помогут вам понять, как писать и структурировать код на C#. Используйте комментарии, осмысленные имена переменных и функций, а также грамотно структурированные блоки кода для создания понятного и поддерживаемого кода.

Понимание лексики C#

Лексика языка C# состоит из ключевых слов, символов и типов.

Ключевые слова

Некоторые предопределенные и зарезервированные ключевые слова, которые вы встретите , включают:

  • using
  • namespace
  • class
  • static
  • int
  • string
  • double
  • bool
  • if
  • switch
  • break
  • while
  • do
  • for
  • foreach

Символы

Некоторые символы, которые вы будете использовать, включают:

  • "
  • '
  • +
  • -
  • *
  • /
  • %
  • @
  • $

Изменение цветовой схемы для синтаксиса

По умолчанию Visual Studio Code отображает ключевые слова C# синим цветом, чтобы их было легче отличать от остального кода. Вы можете настроить цветовую схему:

  1. В Visual Studio Code перейдите в меню Code | Preferences | Color Theme (это находится в меню File на Windows) или нажмите Ctrl или Cmd + K, затем Ctrl или Cmd + T.
  2. Выберите цветовую тему.

Контекстуальные ключевые слова

Есть также контекстуальные ключевые слова, которые имеют специальное значение только в определенном контексте. Таким образом, в языке C# всего около 100 ключевых слов.

Сравнение языков программирования с человеческими языками

В английском языке более 250,000 различных слов. Как же C# обходится всего лишь сотней ключевых слов? И почему C# так сложно изучить, если у него всего 0,0416% от количества слов в английском языке?

Ключевые различия

Одним из ключевых различий между человеческим языком и языком программирования является то, что разработчики могут определять новые «слова» с новыми значениями. Помимо 104 ключевых слов в языке C#, эта серия статей научит вас сотням тысяч «слов», которые определили другие разработчики, а также тому, как определять свои собственные «слова».

Дополнительная информация

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

Помощь в написании правильного кода

Текстовые редакторы, такие как Notepad, не помогут вам писать правильный текст на английском языке. Аналогично, Notepad не поможет вам писать правильный код на C#.

Microsoft Word может помочь вам писать на английском, выделяя орфографические ошибки красными волнистыми линиями и грамматические ошибки синими. Например, Word может подсказать, что «icecream» следует писать как «ice-cream» или «ice cream», а предложения должны начинаться с заглавной буквы.

Использование Visual Studio Code для написания корректного кода

Расширение C# для Visual Studio Code помогает вам писать правильный код на C#, выделяя ошибки так же, как Word выделяет орфографические и грамматические ошибки. Оно постоянно следит за тем, что вы вводите, и дает обратную связь, подчеркивая проблемы цветными волнистыми линиями.

Пример работы расширения:

  1. В файле Program.cs измените L в методе WriteLine на строчную букву.
  2. Удалите точку с запятой в конце оператора.
  3. Перейдите в меню View | Problems или нажмите Ctrl или Cmd + Shift + M и обратите внимание, что под ошибками кода появляются красные волнистые линии, а детали ошибок отображаются в окне PROBLEMS, как показано на следующем скриншоте:

Исправьте две ошибки в коде.

Глаголы как методы

В английском языке глаголы обозначают действия, такие как «run» (бежать) и «jump» (прыгать). В C# глаголы называются методами. Существует сотни тысяч методов в C#. В английском глаголы изменяются в зависимости от времени действия: Amir was jumping (прошедшее), Beth jumps (настоящее), они jumped (прошедшее), Charlie will jump (будущее).

В C# методы, такие как WriteLine, изменяются в зависимости от специфики действия. Это называется перегрузкой, о которой мы подробнее поговорим далее. А пока рассмотрим следующий пример:

// выводит символ возврата каретки
Console.WriteLine();
// выводит приветствие и символ возврата каретки
Console.WriteLine("Hello Ahmed");
// выводит форматированное число и дату и символ возврата каретки
Console.WriteLine("Temperature on {0:D} is {1}°C.", DateTime.Today, 23.4);

Существительные как типы, поля и переменные

В английском языке существительные обозначают имена вещей. Например, Fido — это имя собаки. Слово «dog» (собака) указывает на тип существа, которым является Fido, и для того, чтобы Fido принёс мяч, мы бы использовали его имя.

В C# их эквиваленты — это типы, поля и переменные. Например, Animal и Car — это типы, то есть существительные, которые классифицируют вещи. Head и Engine — это поля, то есть существительные, принадлежащие Animal и Car. А Fido и Bob — это переменные, то есть имена, обозначающие конкретные вещи.

Важное замечание

Стоит отметить, что термин «тип» часто путают с «классом». В игре «Двадцать вопросов» всё можно классифицировать как животное, растение или минерал. В C# каждый тип можно классифицировать как класс, структуру, перечисление, интерфейс или делегат. Ключевое слово C# string является классом, а int — структурой. Поэтому лучше использовать термин «тип» для обоих.

Открытие масштабов словаря C#

В языке C# более 100 ключевых слов, но сколько типов существует? Давайте напишем код, чтобы выяснить, сколько типов и их методов доступно в нашем простом консольном приложении. Пока не беспокойтесь о том, как работает этот код; он использует технику, называемую рефлексией.

  1. Начнем с добавления следующих строк в начале файла Program.cs:
    using System.Linq;
    using System.Reflection;
    
  2. Внутри метода Main, удалите строку, которая выводит «Hello World!», и замените ее следующим кодом:
    // Обходим сборки, на которые ссылается это приложение
    foreach (var r in Assembly.GetEntryAssembly().GetReferencedAssemblies())
    {
        // Загружаем сборку, чтобы прочитать ее детали
        var a = Assembly.Load(new AssemblyName(r.FullName));
        // Объявляем переменную для подсчета количества методов
        int methodCount = 0;
        // Обходим все типы в сборке
        foreach (var t in a.DefinedTypes)
        {
            // Суммируем количество методов
            methodCount += t.GetMethods().Count();
        }
        // Выводим количество типов и их методов
        Console.WriteLine(
            "{0:N0} типов с {1:N0} методами в сборке {2}.",
            arg0: a.DefinedTypes.Count(),
            arg1: methodCount,
            arg2: r.Name);
    }
    
  3. Перейдите в View | Terminal.
  4. В TERMINAL введите следующую команду:
    dotnet run
    
  5. После выполнения команды вы увидите фактическое количество типов и методов, доступных в самом простом приложении при запуске на вашей операционной системе. Количество типов и методов будет различаться в зависимости от операционной системы. Например, вывод на Windows может выглядеть так:
    0 типов с 0 методами в сборке System.Runtime.
    103 типа с 1,094 методами в сборке System.Linq.
    46 типов с 662 методами в сборке System.Console.
    
  6. Добавьте объявления переменных в начале метода Main, как показано ниже:
    static void Main(string[] args)
    {
        // Объявляем некоторые неиспользуемые переменные, используя типы
        // из дополнительных сборок
        System.Data.DataSet ds;
        System.Net.Http.HttpClient client;
    

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

  7. Снова запустите консольное приложение и посмотрите на результаты, которые должны выглядеть примерно так:
    0 типов с 0 методами в сборке System.Runtime.
    376 типов с 6,763 методами в сборке System.Data.Common.
    533 типа с 5,193 методами в сборке System.Net.Http.
    103 типа с 1,094 методами в сборке System.Linq.
    46 типов с 662 методами в сборке System.Console.
    

Теперь вы лучше понимаете, почему изучение C# является сложной задачей, поскольку существует так много типов и методов для изучения. Методы — это только одна категория членов, которые могут быть у типа, и другие программисты постоянно определяют новых «участников» программ!

Работа с переменными

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

Именование и присваивание значений

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

оглашение именования Примеры Использование
Camel case cost, orderDetail, dateOfBirth Локальные переменные, приватные поля
Title case String, Int32, Cost, DateOfBirth, Run Типы, непубличные поля и другие члены, например, методы

Хорошая практика: Следование единому набору соглашений именования облегчает понимание вашего кода другими разработчиками. Подробнее об этом можно прочитать на официальном сайте Microsoft.

Пример объявления локальной переменной и присвоения ей значения с использованием оператора =. В C# 6.0 введен ключевое слово nameof, которое позволяет выводить имя переменной:

// присваиваем переменной heightInMetres значение 1.88 
double heightInMetres = 1.88;
Console.WriteLine($"Переменная {nameof(heightInMetres)} имеет значение {heightInMetres}.");

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

Литералы

При присваивании переменной вы часто присваиваете ей фиксированное значение, называемое литералом. Литерал — это обозначение, представляющее фиксированное значение. Различные типы данных имеют разные обозначения для своих литеральных значений.

Хранение текста

Для текста один символ, например, ‘A’, хранится в типе char и задается с использованием одинарных кавычек вокруг литерального значения или присваивания возвращаемого значения функции, как показано в следующем коде:

char letter = 'A'; // присваивание литеральных символов
char digit = '1';
char symbol = '$';
char userChoice = GetKeystroke(); // присваивание значения из функции

Для текста, состоящего из нескольких символов, например, «Bob», используется тип string, и он задается с использованием двойных кавычек вокруг литерального значения или присваивания возвращаемого значения функции, как показано в следующем коде:

string firstName = "Bob"; // присваивание литеральных строк
string lastName = "Smith";
string phoneNumber = "(215) 555-4256";
// присваивание строки, возвращаемой из функции
string address = GetAddressFromDatabase(id: 563);

Строки с дословным текстом (verbatim strings)

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

string fullNameWithTabSeparator = "Bob\tSmith";

Дополнительная информация: Подробнее об управляющих последовательностях можно прочитать в блоге Microsoft по этой ссылке.

Однако, если вы храните путь к файлу, и одна из папок начинается с буквы «T», пример кода может вызвать проблемы:

string filePath = "C:\televisions\sony\bravia.txt";

Компилятор преобразует \t в символ табуляции, что приведет к ошибкам. Чтобы избежать этого, необходимо использовать дословные строки (verbatim strings), которые начинаются с символа @, как показано в следующем коде:

string filePath = @"C:\televisions\sony\bravia.txt";

Дополнительная информация: Подробнее о дословных строках можно прочитать на официальном сайте Microsoft.

Подытожим:

  • Литеральная строка: Символы заключены в двойные кавычки. Они могут использовать управляющие символы, такие как \t для табуляции.
  • Дословная строка (verbatim string): Литеральная строка, предваряемая символом @, чтобы отключить управляющие символы и оставить обратную косую черту как есть.
  • Интерполированная строка: Литеральная строка, предваряемая символом $, чтобы включить встроенные отформатированные переменные. О таких строках вы узнаете больше позже.

Примеры использования различных строк

  • Литеральная строка:
    string literalString = "Это строка с табуляцией и новой строкой: \tHello\nWorld";
    
  • Дословная строка:
    string verbatimString = @"Это строка с дословным текстом, путь к файлу: C:\televisions\sony\bravia.txt";
    
  • Интерполированная строка:
    string name = "Alice";
    string interpolatedString = $"Hello, {name}! Сегодня {DateTime.Now:dddd}.";
    

Хранение чисел в C#

Числа являются данными, которые мы хотим использовать для выполнения арифметических операций, например, умножения. Телефонный номер не является числом, так как он может содержать символы, не являющиеся цифрами, такие как скобки или дефисы. Если вам не нужно выполнять арифметические операции с числом, и оно содержит нецифровые символы, такие как (414) 555-1234, то это строка.

Числа могут быть натуральными (целыми положительными числами), такими как 42, использоваться для подсчета; они также могут быть отрицательными числами, такими как -42 (называемыми целыми числами); или они могут быть вещественными числами, такими как 3.9 (с дробной частью), которые в программировании называются числами с плавающей точкой одиночной или двойной точности.

Примеры работы с числами

  1. Создайте новую папку внутри папки Chapter02, назовите её Numbers.
  2. В Visual Studio Code откройте папку Numbers.
  3. В терминале создайте новое консольное приложение командой dotnet new console.
  4. Внутри метода Main напишите код для объявления переменных различных числовых типов:
    // unsigned integer означает положительное целое число, включая 0
    uint naturalNumber = 23;
    
    // integer означает отрицательное или положительное целое число, включая 0
    int integerNumber = -23;
    
    // float означает число с плавающей точкой одиночной точности
    // суффикс F делает его литералом типа float
    float realNumber = 2.3F; 
    
    // double означает число с плавающей точкой двойной точности
    double anotherRealNumber = 2.3; // литерал типа double
    

Хранение целых чисел

Компьютеры хранят всю информацию в виде битов, которые могут быть 0 или 1. Это называется двоичной системой счисления. Люди используют десятичную систему счисления.

Десятичная система счисления, известная как Base 10, имеет основание 10, что означает наличие десяти цифр от 0 до 9. В науке, технике и программировании популярны и другие системы счисления. Двоичная система счисления, известная как Base 2, имеет основание 2, что означает наличие двух цифр, 0 и 1.

Пример хранения числа 10 в двоичной системе:

128 64 32 16 8 4 2 1
0 0 0 0 1 0 1 0

Таким образом, 10 в десятичной системе представляется как 00001010 в двоичной системе.

Примеры использования чисел в различных системах счисления

В C# 7.0 и новее можно использовать символ подчёркивания _ в качестве разделителя цифр и поддерживаются двоичные литералы. Можно вставлять подчёркивания в любом месте числового литерала для улучшения читаемости, например, 1_000_000.

Для использования двоичной нотации (Base 2) используйте 0b в начале литерала. Для шестнадцатеричной нотации (Base 16) используйте 0x. Примеры:

// три переменные, которые хранят число 2 миллиона
int decimalNotation = 2_000_000;
int binaryNotation = 0b_0001_1110_1000_0100_1000_0000;
int hexadecimalNotation = 0x_001E_8480;

// проверка, что все три переменные имеют одно и то же значение
// оба оператора выводят true
Console.WriteLine($"{decimalNotation == binaryNotation}");
Console.WriteLine($"{decimalNotation == hexadecimalNotation}");

Запуск кода

  1. Добавьте код в метод Main в вашем консольном приложении.
  2. Запустите консольное приложение командой dotnet run.
  3. Обратите внимание на результат, который показывает, что все три числа одинаковы:
    True
    True
    

Компьютеры всегда могут точно представить целые числа, используя тип int или один из его аналогов, таких как long и short.

Хранение вещественных чисел

Компьютеры не всегда могут точно представить числа с плавающей запятой. Типы float и double хранят вещественные числа с одинарной и двойной точностью соответственно. В большинстве языков программирования используется стандарт IEEE для арифметики с плавающей запятой.

Представление числа 12.75 в двоичной системе

Таблица ниже показывает упрощенное представление числа 12.75 в двоичной нотации. Обратите внимание на биты со значением 1 в столбцах 8, 4, ½ и ¼:

128 64 32 16 8 4 2 1 . ½ ¼ 1/8 1/16
0 0 0 0 1 1 0 0 . 1 1 0 0

Таким образом, 12.75 в десятичной системе представляется как 00001100.1100 в двоичной системе. Некоторые числа не могут быть точно представлены таким образом.

Исследование размеров чисел

C# имеет оператор sizeof(), который возвращает количество байт, занимаемых типом в памяти. Некоторые типы имеют члены MinValue и MaxValue, возвращающие минимальные и максимальные значения, которые можно хранить в переменной этого типа.

  1. Внутри метода Main введите код, показывающий размер трёх числовых типов данных:
    Console.WriteLine($"int uses {sizeof(int)} bytes and can store numbers in the range {int.MinValue:N0} to {int.MaxValue:N0}.");
    Console.WriteLine($"double uses {sizeof(double)} bytes and can store numbers in the range {double.MinValue:N0} to {double.MaxValue:N0}.");
    Console.WriteLine($"decimal uses {sizeof(decimal)} bytes and can store numbers in the range {decimal.MinValue:N0} to {decimal.MaxValue:N0}.");
    
  2. Запустите консольное приложение командой dotnet run, и вы увидите следующий результат:
    int uses 4 bytes and can store numbers in the range -2,147,483,648 to 2,147,483,647.
    double uses 8 bytes and can store numbers in the range -1.79769313486232E+308 to 1.79769313486232E+308.
    decimal uses 16 bytes and can store numbers in the range -79,228,162,514,264,337,593,543,950,335 to 79,228,162,514,264,337,593,543,950,335.
    

Сравнение типов double и decimal

  1. Введите следующий код для сравнения значений типов double:
    Console.WriteLine("Using doubles:");
    double a = 0.1;
    double b = 0.2;
    if (a + b == 0.3)
    {
        Console.WriteLine($"{a} + {b} equals 0.3");
    }
    else
    {
        Console.WriteLine($"{a} + {b} does NOT equal 0.3");
    }
    

    Запустите приложение, и результат будет следующим:

    Using doubles:
    0.1 + 0.2 does NOT equal 0.3
    

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

  2. Измените код для использования типа decimal:
    Console.WriteLine("Using decimals:");
    decimal c = 0.1M; // суффикс M означает, что это литерал типа decimal
    decimal d = 0.2M;
    if (c + d == 0.3M)
    {
        Console.WriteLine($"{c} + {d} equals 0.3");
    }
    else
    {
        Console.WriteLine($"{c} + {d} does NOT equal 0.3");
    }
    

    Запустите приложение, и результат будет следующим:

    Using decimals:
    0.1 + 0.2 equals 0.3
    

    Тип decimal точен, так как он хранит число как большое целое и сдвигает десятичную точку.

 

Заключение

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

Хранение булевых значений

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

bool happy = true;
bool sad = false;

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

Использование рабочих областей в Visual Studio Code

Прежде чем мы создадим ещё несколько проектов, давайте поговорим о рабочих областях. Хотя мы могли бы продолжать создавать и открывать отдельные папки для каждого проекта, иногда полезно иметь несколько папок открытыми одновременно. Visual Studio Code имеет функцию под названием «workspaces» (рабочие области), которая это позволяет.

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

  1. В Visual Studio Code перейдите в меню File и выберите Save Workspace As….
  2. Введите Chapter02 как имя рабочей области, выберите папку Chapter02 и нажмите Save.Сохранение рабочей области в VSCode
  3. Перейдите в меню File и выберите Add Folder to Workspace….
  4. Выберите папку Basics, нажмите Add, и убедитесь, что папки Basics и Numbers теперь являются частью рабочей области Chapter02.

Создание и управление рабочими областями

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

  • Открытие рабочей области: Для открытия сохраненной рабочей области, перейдите в меню File и выберите Open Workspace, затем выберите сохраненную рабочую область.
  • Добавление папок: Вы можете добавлять дополнительные папки в вашу рабочую область в любое время, используя команду Add Folder to Workspace… из меню File.
  • Удаление папок: Чтобы удалить папку из рабочей области, щелкните правой кнопкой мыши на папке в Explorer и выберите Remove Folder from Workspace.
  • Навигация по папкам в Terminal: При использовании рабочей области убедитесь, что вы находитесь в правильной папке перед вводом команд в Terminal. Это поможет избежать потенциально разрушительных команд в неправильной папке.

Практическое использование рабочих областей

Рабочие области упрощают управление несколькими проектами и позволяют легко переключаться между ними. Это особенно полезно для больших проектов с несколькими компонентами или для работы над несколькими проектами одновременно.

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

Хранение объектов любого типа

Тип данных object в C# может хранить любой тип данных, но использование этого типа имеет свои недостатки, такие как более сложный код и возможное снижение производительности. Поэтому следует избегать его использования, когда это возможно. Тем не менее, бывают случаи, когда использование object необходимо. Давайте посмотрим, как использовать object тип в C#:

  1. Создание нового проекта:
    • Создайте новую папку с именем Variables и добавьте её в рабочую область Chapter02.
  2. Создание нового консольного приложения:
    • Перейдите в Terminal | New Terminal.
    • Выберите проект Variables.
    • Введите команду для создания нового консольного приложения: dotnet new console.
  3. Настройка проекта:
    • Перейдите в View | Command Palette.
    • Введите и выберите OmniSharp: Select Project.
    • Выберите проект Variables и, если появится запрос, нажмите Yes, чтобы добавить необходимые компоненты для отладки.
  4. Работа с object:
    • В EXPLORER откройте файл Program.cs в проекте Variables.
    • В методе Main добавьте следующие операторы для объявления и использования переменных типа object:
      using System;
      
      class Program
      {
          static void Main(string[] args)
          {
              object height = 1.88; // хранение double в object
              object name = "Amir"; // хранение строки в object
              Console.WriteLine($"{name} is {height} metres tall.");
      
              //int length1 = name.Length; // выдаёт ошибку компиляции!
              int length2 = ((string)name).Length; // сообщаем компилятору, что это строка
              Console.WriteLine($"{name} has {length2} characters.");
          }
      }
      
  5. Запуск и тестирование кода:
    • В TERMINAL выполните команду dotnet run и обратите внимание, что четвёртое выражение не компилируется, потому что компилятор не знает тип данных переменной name.
  6. Закомментируйте проблемный код:
    //int length1 = name.Length; // выдаёт ошибку компиляции!
    
    • Добавьте двойные слэши в начало строки, которая не компилируется, чтобы закомментировать её.
  7. Повторный запуск кода:
    • В TERMINAL выполните команду dotnet run снова и убедитесь, что компилятор может получить доступ к длине строки, если явно указать тип переменной, как показано в следующем выводе:
      Amir is 1.88 metres tall.
      Amir has 4 characters.
      

Заключение

Тип object был доступен с первой версии C#, но начиная с версии 2.0 появился более эффективный и гибкий способ работы с различными типами данных — это обобщения (generics). Обобщения позволяют избежать накладных расходов на производительность и сложность кода, связанных с использованием object.

Хранение динамических типов

Ключевое слово dynamic в C# позволяет хранить любой тип данных, обеспечивая ещё большую гибкость, чем object. Однако, это также приводит к снижению производительности, поскольку тип данных проверяется во время выполнения, а не на этапе компиляции. Давайте рассмотрим, как использовать тип dynamic.

  1. Создание переменной типа dynamic:
    • В методе Main добавьте следующий код для объявления переменной типа dynamic и присвоения ей строкового значения:
      // хранение строки в динамическом объекте
      dynamic anotherName = "Ahmed";
      
  2. Получение длины строки:
    // этот код компилируется, но вызовет исключение во время выполнения,
    // если позже в переменную будет сохранён тип данных, не имеющий свойства Length
    int length = anotherName.Length;
    
    • Добавьте следующий код для получения длины строки, хранящейся в переменной anotherName:

Пример кода

Полный пример использования типа dynamic в консольном приложении:

using System;

class Program
{
    static void Main(string[] args)
    {
        // хранение строки в динамическом объекте
        dynamic anotherName = "Ahmed";
        Console.WriteLine($"{anotherName} is {anotherName.Length} characters long.");
        
        // изменение типа данных, хранящегося в dynamic
        anotherName = 12345;
        try
        {
            // попытка получить свойство Length у целого числа вызовет исключение
            Console.WriteLine($"{anotherName} has length {anotherName.Length}");
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
        {
            Console.WriteLine($"Exception: {e.Message}");
        }
    }
}

Вывод и объяснение

Когда вы запустите это приложение, вы увидите следующий вывод:

Ahmed is 5 characters long.
Exception: 'int' does not contain a definition for 'Length'

В этом примере:

  • Переменной anotherName сначала присваивается строка "Ahmed", и мы можем получить длину строки.
  • Затем anotherName присваивается целое число 12345. При попытке получить свойство Length, которое недоступно для целого числа, будет выброшено исключение RuntimeBinderException.

Ограничения динамического типа

Использование типа dynamic имеет некоторые ограничения:

  1. Отсутствие IntelliSense:
    • Visual Studio Code не может показать IntelliSense для динамических типов, так как компилятор не знает тип данных на этапе компиляции.
  2. Проверка типов во время выполнения:
    • Компилятор не проверяет тип данных во время сборки, и все ошибки будут выявлены только во время выполнения, что может привести к исключениям.

Заключение

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

Отличия между object и dynamic

1. object (системный тип):

  • Базовый тип: object является базовым типом для всех типов данных в C#. Любой тип может быть присвоен переменной типа object.
  • Компиляция: Тип переменной object определяется во время компиляции. Компилятор знает, что переменная имеет тип object, но не знает, что именно в ней хранится, пока не будет выполнено приведение типа (cast).
  • Неявное преобразование: Чтобы использовать конкретные члены объекта, хранящегося в переменной типа object, необходимо выполнить явное приведение типа (cast) к нужному типу.
  • Интеллисенс: Visual Studio предоставляет поддержку IntelliSense для переменных типа object после приведения типа.
  • Производительность: Использование типа object требует дополнительных операций приведения типов (casting), что может немного снижать производительность.

2. dynamic (динамический тип):

  • Динамическое разрешение: Тип переменной dynamic определяется во время выполнения. Компилятор не проверяет корректность обращения к членам объекта на этапе компиляции.
  • Неявное приведение: Переменные типа dynamic автоматически приводятся к любому типу без явного приведения. Вы можете обращаться к членам объекта без предварительного приведения типа.
  • Интеллисенс: Visual Studio не предоставляет поддержку IntelliSense для переменных типа dynamic, так как компилятор не знает их тип на этапе компиляции.
  • Производительность: Использование типа dynamic может существенно снижать производительность из-за необходимости разрешения типов и членов во время выполнения.

Когда использовать object или dynamic

Использование object:

  1. Когда тип данных известен, но может быть разным:
    • Например, если у вас есть метод, который должен принимать параметры разных типов, но все они будут обработаны одинаково.
    • Пример: метод для вывода значения переменной на консоль, который принимает параметр типа object.
      void PrintValue(object value)
      {
          Console.WriteLine(value);
      }
      
  2. Когда важна проверка типов на этапе компиляции:
    • Использование object обеспечивает проверку типов во время компиляции, что помогает избежать ошибок на этапе выполнения.
    • Например, при работе с коллекциями типа object и явном приведении типов.
      object number = 42;
      int intValue = (int)number;
      

Использование dynamic:

  1. Когда тип данных неизвестен до выполнения:
    • Например, при работе с динамическими данными из JSON, XML или других источников данных, тип которых может меняться.
    • Пример: динамическая обработка JSON-ответа от веб-сервиса.
      dynamic jsonResponse = GetJsonResponse();
      Console.WriteLine(jsonResponse.name);
      
  2. Когда нужно динамически обращаться к членам объекта:
    • Например, при работе с COM-объектами или библиотеками, которые не имеют статической типизации.
    • Пример: работа с объектами Office Interop.
      dynamic excelApp = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
      excelApp.Visible = true;

Заключение

  • Используйте object, когда вам нужно работать с переменными разных типов и вы хотите воспользоваться преимуществами статической типизации и проверки типов на этапе компиляции.
  • Используйте dynamic, когда вам нужна максимальная гибкость и тип данных может изменяться во время выполнения, либо когда вы работаете с данными из внешних источников, где тип данных заранее неизвестен.

Объявление локальных переменных

Локальные переменные объявляются внутри методов и существуют только в течение выполнения этого метода. Когда метод завершает выполнение, память, выделенная для любых локальных переменных, освобождается. Значимые типы (value types) освобождаются сразу, тогда как ссылочные типы (reference types) должны ждать сборки мусора (garbage collection).

Указание и вывод типа локальной переменной

Примеры явного указания типа локальных переменных

int population = 66_000_000; // 66 миллионов в Великобритании
double weight = 1.88; // в килограммах
decimal price = 4.99M; // в фунтах стерлингов
string fruit = "Apples"; // строки используют двойные кавычки
char letter = 'Z'; // символы используют одинарные кавычки
bool happy = true; // логические значения имеют значение true или false

Visual Studio Code покажет зеленые подчеркивания под именами переменных, предупреждая, что переменная назначена, но ее значение не используется.

Примеры использования ключевого слова var для вывода типа

Вы можете использовать ключевое слово var для объявления локальных переменных. Компилятор выведет тип из значения, которое вы присваиваете после оператора присваивания (=).

var population = 66_000_000; // 66 миллионов в Великобритании
var weight = 1.88; // в килограммах
var price = 4.99M; // в фунтах стерлингов
var fruit = "Apples"; // строки используют двойные кавычки
var letter = 'Z'; // символы используют одинарные кавычки
var happy = true; // логические значения имеют значение true или false

Примечание о хорошей практике

Хотя использование var удобно, некоторые разработчики избегают его использования, чтобы облегчить понимание типов переменных при чтении кода. Лично я использую var только когда тип очевиден. Например:

using System.IO;
using System.Xml;

var xml1 = new XmlDocument(); // хороший пример использования var, избегает повторения типа
XmlDocument xml2 = new XmlDocument(); // явное указание типа

var file1 = File.CreateText(@"C:\something.txt"); // плохой пример использования var, тип не очевиден
StreamWriter file2 = File.CreateText(@"C:\something.txt"); // явное указание типа

Когда использовать var или явное указание типа

  • Используйте var, когда тип очевиден из контекста и выводится из присваиваемого значения, что делает код короче и чище:
    var list = new List<int>(); // очевидный тип
    
  • Используйте явное указание типа, когда тип не очевиден или важен для понимания кода при чтении:
    StreamWriter writer = File.CreateText(@"C:\something.txt"); // явное указание типа
    

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

Использование target-typed new для создания объектов

В C# 9 Microsoft представила новый синтаксис для создания объектов, известный как target-typed new. Этот синтаксис позволяет указать тип переменной, а затем использовать new без повторения типа, как показано в следующем примере:

XmlDocument xml3 = new(); // target-typed new в C# 9

Получение значений по умолчанию для типов

Большинство примитивных типов, кроме string, являются значимыми типами (value types), что означает, что они должны иметь значение. Вы можете определить значение по умолчанию для типа, используя оператор default().

Тип string является ссылочным типом (reference type). Это означает, что переменные типа string содержат адрес памяти значения, а не само значение. Ссылочный тип может иметь значение null, которое указывает, что переменная пока ни на что не ссылается. null является значением по умолчанию для всех ссылочных типов.

Пример кода для исследования значений по умолчанию

  1. В методе Main добавьте операторы для отображения значений по умолчанию для int, bool, DateTime и string, как показано в следующем коде:
    Console.WriteLine($"default(int) = {default(int)}");
    Console.WriteLine($"default(bool) = {default(bool)}");
    Console.WriteLine($"default(DateTime) = {default(DateTime)}");
    Console.WriteLine($"default(string) = {default(string)}");
    
  2. Запустите консольное приложение и посмотрите результат. Обратите внимание, что вывод для даты и времени может быть отформатирован по-другому, в зависимости от региональных настроек. Пример ожидаемого вывода:
    default(int) = 0
    default(bool) = False
    default(DateTime) = 01/01/0001 00:00:00
    default(string) = 
    

Объяснение результатов

  • default(int) возвращает 0, так как это значение по умолчанию для типа int.
  • default(bool) возвращает False, так как это значение по умолчанию для типа bool.
  • default(DateTime) возвращает 01/01/0001 00:00:00, что является значением по умолчанию для DateTime.
  • default(string) возвращает пустую строку, так как это значение по умолчанию для ссылочного типа string, что соответствует null.

Сравнение типов данных и область их применения

Тип данных Размер Особенности применения
byte 1 байт Беззнаковое целое число от 0 до 255
sbyte 1 байт Знаковое целое число от -128 до 127
short 2 байта Знаковое целое число от -32,768 до 32,767
ushort 2 байта Беззнаковое целое число от 0 до 65,535
int 4 байта Знаковое целое число от -2,147,483,648 до 2,147,483,647
uint 4 байта Беззнаковое целое число от 0 до 4,294,967,295
long 8 байт Знаковое целое число от -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807
ulong 8 байт Беззнаковое целое число от 0 до 18,446,744,073,709,551,615
float 4 байта Число с плавающей точкой одинарной точности от ±1.5 x 10^−45 до ±3.4 x 10^38
double 8 байт Число с плавающей точкой двойной точности от ±5.0 × 10^−324 до ±1.7 × 10^308
decimal 16 байт Десятичное дробное число высокой точности от ±1.0 x 10^-28 до ±7.9 x 10^28, используется в финансовых вычислениях
char 2 байта Символ Unicode от ‘\u0000’ до ‘\uffff’
bool 1 байт (реально: 4 байта) Логическое значение true или false
string Зависит от длины строки Строка символов Unicode, ссылочный тип
object 4 байта (32-битная система) или 8 байт (64-битная система) Базовый тип для всех типов данных в C#
dynamic Зависит от данных Может хранить любое значение, тип определяется во время выполнения программы
DateTime 8 байт Представляет дату и время

Применение различных типов данных:

  • byte и sbyte: Используются для хранения небольших целых чисел и экономии памяти.
  • short и ushort: Полезны для экономии памяти в случаях, когда диапазон int слишком велик.
  • int и uint: Наиболее часто используемые типы для целых чисел в общих вычислениях.
  • long и ulong: Применяются, когда требуется хранить очень большие целые числа.
  • float и double: Используются для чисел с плавающей точкой в научных и инженерных вычислениях.
  • decimal: Применяется для финансовых вычислений, где важна высокая точность.
  • char: Используется для представления отдельных символов.
  • bool: Используется для логических значений и условий.
  • string: Применяется для работы с текстовыми данными.
  • object: Базовый тип для всех данных в C#, используется для хранения значений любого типа, но требует явного приведения типов.
  • dynamic: Позволяет создавать переменные, тип которых определяется во время выполнения, полезен для работы с динамическими данными, но может снизить производительность.
  • DateTime: Используется для представления и работы с датой и временем.

Операторы и методы для работы с типами данных в C#

В C# существует множество операторов и методов для работы с типами данных. Ниже представлены основные операторы и методы для различных типов данных, а также способы их преобразования.

Операторы

Аритметические операторы

  • + (сложение): Складывает два числа.
  • - (вычитание): Вычитает одно число из другого.
  • * (умножение): Умножает два числа.
  • / (деление): Делит одно число на другое.
  • % (остаток от деления): Возвращает остаток от деления одного числа на другое.

Логические операторы

  • && (логическое И): Возвращает true, если оба операнда истинны.
  • || (логическое ИЛИ): Возвращает true, если хотя бы один из операндов истинен.
  • ! (логическое НЕ): Инвертирует значение операнда.

Операторы сравнения

  • == (равно): Проверяет равенство двух значений.
  • != (не равно): Проверяет неравенство двух значений.
  • > (больше): Проверяет, больше ли одно значение другого.
  • < (меньше): Проверяет, меньше ли одно значение другого.
  • >= (больше или равно): Проверяет, больше ли одно значение другого или равно ему.
  • <= (меньше или равно): Проверяет, меньше ли одно значение другого или равно ему.

Операторы присваивания

  • = (присваивание): Присваивает значение переменной.
  • += (сложение с присваиванием): Складывает и присваивает значение.
  • -= (вычитание с присваиванием): Вычитает и присваивает значение.
  • *= (умножение с присваиванием): Умножает и присваивает значение.
  • /= (деление с присваиванием): Делит и присваивает значение.
  • %= (остаток с присваиванием): Вычисляет остаток и присваивает значение.

Методы для работы с типами данных

Методы для строк (string)

  • Length: Возвращает длину строки.
  • ToUpper(): Преобразует строку в верхний регистр.
  • ToLower(): Преобразует строку в нижний регистр.
  • Substring(int startIndex, int length): Возвращает подстроку из строки.
  • IndexOf(string value): Возвращает индекс первого вхождения подстроки.
  • Replace(string oldValue, string newValue): Заменяет все вхождения одной подстроки на другую.

Методы для чисел (int, double, decimal)

  • Math.Abs(): Возвращает абсолютное значение числа.
  • Math.Max(): Возвращает большее из двух чисел.
  • Math.Min(): Возвращает меньшее из двух чисел.
  • Math.Round(): Округляет число до ближайшего целого или указанного количества десятичных знаков.
  • Math.Pow(double base, double exponent): Возводит число в степень.
  • Math.Sqrt(double value): Возвращает квадратный корень из числа.

Преобразование типов

Неявное преобразование

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

int a = 10;
double b = a; // Неявное преобразование int в double

Явное преобразование (кастинг)

Явное преобразование требуется, когда возможна потеря данных или требуется указать на преобразование явно:

double a = 10.5;
int b = (int)a; // Явное преобразование double в int

Методы преобразования

  • Convert.ToInt32(): Преобразует значение в int.
  • Convert.ToDouble(): Преобразует значение в double.
  • Convert.ToDecimal(): Преобразует значение в decimal.
  • Convert.ToString(): Преобразует значение в string.
  • int.Parse(string): Преобразует строку в int.
  • double.Parse(string): Преобразует строку в double.
  • decimal.Parse(string): Преобразует строку в decimal.

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

string numberString = "12345";
int number = int.Parse(numberString); // Преобразование строки в int
double doubleNumber = Convert.ToDouble(numberString); // Преобразование строки в double
string backToString = number.ToString(); // Преобразование int обратно в строку

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

using System;

class Program
{
    static void Main()
    {
        // Аритметические операторы
        int x = 10;
        int y = 20;
        int sum = x + y;
        Console.WriteLine($"Сумма: {sum}");

        // Логические операторы
        bool isSunny = true;
        bool isWeekend = false;
        if (isSunny && isWeekend)
        {
            Console.WriteLine("Пойду на пляж!");
        }
        else
        {
            Console.WriteLine("Останусь дома.");
        }

        // Операторы сравнения
        if (x > y)
        {
            Console.WriteLine("x больше y");
        }
        else
        {
            Console.WriteLine("x не больше y");
        }

        // Преобразование типов
        double a = 9.99;
        int b = (int)a; // Явное преобразование
        Console.WriteLine($"a: {a}, b: {b}");

        // Методы для строк
        string text = "Hello, World!";
        string upperText = text.ToUpper();
        Console.WriteLine($"Uppercase: {upperText}");
        int length = text.Length;
        Console.WriteLine($"Length: {length}");

        // Методы для чисел
        double number = -123.45;
        double absoluteNumber = Math.Abs(number);
        Console.WriteLine($"Абсолютное значение: {absoluteNumber}");

        // Преобразование типов с использованием методов
        string numberString = "12345";
        int parsedNumber = int.Parse(numberString);
        Console.WriteLine($"Преобразованное число: {parsedNumber}");
    }
}

Хранение нескольких значений

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

Следующий код выделяет память для массива, в котором будут храниться четыре строковых значения. Затем он сохраняет строковые значения по индексам от 0 до 3 (нумерация в массивах начинается с нуля, поэтому последний элемент будет иметь индекс, на единицу меньше длины массива). В конце он проходит по каждому элементу массива с помощью оператора for, который мы рассмотрим далее.

Как использовать массив:

  1. В папке Chapter02 создайте новую папку с именем Arrays.
  2. Добавьте папку Arrays в рабочее пространство Chapter02.
  3. Откройте новое окно терминала для проекта Arrays.
  4. Создайте новый проект консольного приложения в папке Arrays.
  5. Выберите Arrays как текущий проект для OmniSharp.
  6. В проекте Arrays, в файле Program.cs, в методе Main добавьте следующие операторы для объявления и использования массива строковых значений:
    string[] names; // можно ссылаться на любой массив строк
    // выделение памяти для четырех строк в массиве
    names = new string[4];
    // сохранение элементов по индексам
    names[0] = "Kate";
    names[1] = "Jack"; 
    names[2] = "Rebecca"; 
    names[3] = "Tom";
    // проход по элементам массива
    for (int i = 0; i < names.Length; i++)
    {
        // вывод элемента по индексу i
        Console.WriteLine(names[i]); 
    }
    
  7. Запустите консольное приложение и обратите внимание на результат:
    Kate
    Jack
    Rebecca
    Tom
    

Массивы всегда имеют фиксированный размер во время выделения памяти, поэтому вам нужно заранее решить, сколько элементов вы хотите сохранить.

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

Работа с пустыми значениями (null)

Теперь вы знаете, как сохранять примитивные значения, такие как числа, в переменных. Но что, если переменная еще не имеет значения? Как это указать? В C# существует понятие пустого значения (null), которое можно использовать, чтобы указать, что переменная не была установлена.

Создание значимого типа с возможностью хранения null

По умолчанию, значимые типы, такие как int и DateTime, всегда должны иметь значение, что и отражено в их названии. Однако иногда, например, при чтении значений из базы данных, где допускаются пустые или отсутствующие значения, удобно, чтобы значимый тип мог принимать значение null. Это называется nullable value type.

Вы можете включить это, добавив вопросительный знак в качестве суффикса к типу при объявлении переменной. Рассмотрим пример:

  1. В папке Chapter02 создайте новую папку с именем NullHandling.
  2. Добавьте папку NullHandling в рабочее пространство Chapter02.
  3. Откройте новое окно терминала для проекта NullHandling.
  4. Создайте новый проект консольного приложения в папке NullHandling.
  5. Выберите NullHandling как текущий проект для OmniSharp.
  6. В проекте NullHandling, в файле Program.cs, в методе Main добавьте следующие операторы для объявления и присвоения значений, включая null, переменным типа int:
    int thisCannotBeNull = 4;
    // thisCannotBeNull = null; // ошибка компиляции!
    int? thisCouldBeNull = null; // nullable int
    Console.WriteLine(thisCouldBeNull); // выводит пустую строку, так как значение null
    Console.WriteLine(thisCouldBeNull.GetValueOrDefault()); // выводит 0, так как значение null
    thisCouldBeNull = 7; // присваиваем значение 7
    Console.WriteLine(thisCouldBeNull); // выводит 7
    Console.WriteLine(thisCouldBeNull.GetValueOrDefault()); // также выводит 7
    
  7. Закомментируйте строку, которая вызывает ошибку компиляции.
  8. Запустите приложение и обратите внимание на результат:
    0
    7
    7
    

Первая строка пустая, потому что она выводит значение null!

Объяснение кода:

  • int thisCannotBeNull = 4;: Объявляем переменную типа int, которая не может быть null.
  • // thisCannotBeNull = null;: Закомментированная строка, так как попытка присвоить null значимому типу вызывает ошибку компиляции.
  • int? thisCouldBeNull = null;: Объявляем переменную nullable int и присваиваем ей значение null.
  • Console.WriteLine(thisCouldBeNull);: Выводим значение nullable переменной, результатом будет пустая строка, так как значение null.
  • Console.WriteLine(thisCouldBeNull.GetValueOrDefault());: Выводим значение с использованием метода GetValueOrDefault(), который возвращает 0 для null.
  • thisCouldBeNull = 7;: Присваиваем значение 7 nullable переменной.
  • Console.WriteLine(thisCouldBeNull);: Выводим новое значение переменной, результатом будет 7.
  • Console.WriteLine(thisCouldBeNull.GetValueOrDefault());: Выводим значение с использованием метода GetValueOrDefault(), результатом будет также 7.

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

Понимание nullable ссылочных типов

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

Важная информация:

Вы можете узнать больше по следующей ссылке, где изобретатель null, сэр Чарльз Энтони Ричард Хоар, признает свою ошибку в записанной часовой лекции: The Billion Dollar Mistake.

Наиболее значительным изменением языка в C# 8.0 стало введение nullable и non-nullable ссылочных типов. Вы можете подумать: «Но подождите, разве ссылочные типы уже не могут быть null?». И вы будете правы, но в C# 8.0 и позже, ссылочные типы могут быть настроены таким образом, чтобы больше не допускать значения null, путем установки опции на уровне файла или проекта для включения этой полезной новой функции. Поскольку это большое изменение для C#, Microsoft решила сделать эту функцию доступной по выбору.

Это нововведение будет влиять на разработку на протяжении нескольких лет, так как существует тысячи существующих библиотек и приложений, которые ожидают старого поведения. Даже у Microsoft не было времени полностью реализовать эту новую функцию во всех основных пакетах .NET 5.

Важная информация:

Вы можете прочитать твит о достижении 80% аннотаций в .NET 5 по следующей ссылке: 80% annotations in .NET 5.

Переход на новую функцию в ваших проектах:

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

  • По умолчанию: Изменения не требуются. Non-nullable ссылочные типы не поддерживаются.
  • Opt-in проект, opt-out файлы: Включите функцию на уровне проекта и для любых файлов, которые должны оставаться совместимыми со старым поведением, отключите её. Этот подход Microsoft использует для обновления своих пакетов.
  • Opt-in файлы: Включайте функцию только для отдельных файлов.

Как включить и использовать nullable и non-nullable ссылочные типы

Включение в проекте

Чтобы включить поддержку nullable и non-nullable ссылочных типов на уровне проекта, добавьте следующую строку в файл конфигурации проекта (например, .csproj):

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Использование в коде

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

Пример:

#nullable enable

public class Example
{
    public string NonNullableString { get; set; } // Не может быть null
    public string? NullableString { get; set; }   // Может быть null

    public void Demonstrate()
    {
        NonNullableString = "Hello";
        NullableString = null;

        Console.WriteLine(NonNullableString.Length); // Безопасно, так как не может быть null
        if (NullableString != null)
        {
            Console.WriteLine(NullableString.Length); // Проверка на null перед использованием
        }
    }
}

Опции управления null-ссылочными типами

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

  • #nullable enable: Включает проверки nullable для кода ниже этой строки.
  • #nullable disable: Отключает проверки nullable.
  • #nullable restore: Восстанавливает состояние nullable до уровня настройки проекта.

Эти директивы можно использовать для включения или отключения проверки nullable в отдельных файлах или частях кода.

Объявление ненулевых переменных и параметров

Если вы включили поддержку nullable ссылочных типов и хотите, чтобы ссылочный тип мог принимать значение null, вам нужно использовать тот же синтаксис, что и для nullable значимых типов, а именно добавить символ ? после объявления типа.

Как работают nullable ссылочные типы?

Рассмотрим пример. При хранении информации об адресе, вы можете захотеть, чтобы улица, город и регион имели значение, но здание могло быть оставлено пустым, то есть null:

  1. В NullHandling.csproj добавьте элемент для включения nullable ссылочных типов, как показано в следующем коде:
    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    </Project>
    
  2. В Program.cs в начале файла добавьте директиву для включения nullable ссылочных типов:
    #nullable enable
    
  3. В Program.cs в пространстве имен NullHandling, перед классом Program, добавьте класс Address с четырьмя полями:
    class Address
    {
        public string? Building;
        public string Street = string.Empty;
        public string City = string.Empty;
        public string Region = string.Empty;
    }
    

    Обратите внимание, что поля Street, City и Region инициализированы пустыми строками, так как они не могут быть null.

  4. В методе Main добавьте код для создания экземпляра Address и установки его свойств:
    var address = new Address();
    address.Building = null; // Допустимо, так как Building может быть null
    address.Street = null;   // Предупреждение: Street не может быть null
    address.City = "London"; 
    address.Region = null;   // Предупреждение: Region не может быть null
    

Компилятор выдаст предупреждения о присвоении значения null полям Street и Region, так как они объявлены как ненулевые. Эта новая функция C# 8.0 позволяет явно указывать, какие ссылочные типы могут быть null, а какие — нет, что помогает избежать ошибок, связанных с null значениями. Это шаг к более безопасному и надежному коду.

Дополнительная информация

Вы можете посмотреть видео о том, как навсегда избавиться от ошибок null reference по следующей ссылке: Как избавиться от null reference exceptions навсегда.

Проверка на null

Проверка на null важна при работе с nullable ссылочными и значимыми типами, так как использование переменной, содержащей null, может привести к исключению NullReferenceException. Чтобы избежать ошибок, необходимо проверять переменную на null перед её использованием, как показано в следующем примере:

// проверка, что переменная не равна null перед использованием
if (thisCouldBeNull != null)
{
    // доступ к члену thisCouldBeNull
    int length = thisCouldBeNull.Length; // может вызвать исключение
    // дальнейшие действия
}

Оператор null-условия

Если вам нужно использовать член переменной, которая может быть null, вы можете использовать оператор null-условия ?., как в следующем примере:

string authorName = null;
// следующий код вызовет NullReferenceException
int x = authorName.Length;
// вместо вызова исключения, переменной y будет присвоено значение null
int? y = authorName?.Length;

Оператор null-объединения

Иногда нужно присвоить переменной результат или использовать альтернативное значение, если переменная равна null. Для этого используется оператор null-объединения ??, как показано в следующем примере:

// результат будет равен 3, если authorName?.Length равно null
var result = authorName?.Length ?? 3; 
Console.WriteLine(result);

Дополнительная информация

Больше информации об операторе null-условия можно найти здесь.

Больше информации об операторе null-объединения можно найти здесь.

Адаптированный код с примерами

  1. Создайте новый проект и добавьте в него следующий код:
    using System;
    
    namespace NullHandling
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Пример с nullable значимыми типами
                int? nullableInt = null;
    
                if (nullableInt != null)
                {
                    // Использование nullable переменной
                    Console.WriteLine($"nullableInt имеет значение: {nullableInt}");
                }
                else
                {
                    Console.WriteLine("nullableInt равно null");
                }
    
                // Пример с null-условным оператором
                string authorName = null;
                int? nameLength = authorName?.Length;
                Console.WriteLine($"Длина имени автора: {nameLength}"); // Результат будет null
    
                // Пример с оператором null-объединения
                int result = authorName?.Length ?? 3;
                Console.WriteLine($"Результат: {result}"); // Результат будет 3
            }
        }
    }
    
  2. Запустите приложение и обратите внимание на вывод в консоли:
    nullableInt равно null
    Длина имени автора: 
    Результат: 3
    

Этот код демонстрирует, как проверять значения на null, использовать оператор null-условия и оператор null-объединения в C#.

Задания для тренировки

Задание 1

Создать две целочисленные переменные, сложить их и вывести результат на экран.

Задание 2

Создать две переменные типа double, умножить их и вывести результат на экран.

Задание 3

Создать две переменные типа string, объединить их и вывести результат на экран.

Задание 4

Преобразовать строку в целое число и вывести результат.

Задание 5

Создать переменную типа bool и вывести её значение на экран.

Задание 6

Создать переменную типа char, преобразовать её в строку и объединить с другой строкой, затем вывести результат на экран.

Задание 7

Создать переменную типа decimal, увеличить её значение на 10% и вывести результат на экран.

Задание 8

Создать массив целых чисел, найти его сумму и вывести результат на экран.

Задание 9

Создать переменную типа DateTime, прибавить к ней 10 дней и вывести результат на экран.

Задание 10

Создать две переменные типа float, разделить одну на другую и вывести результат на экран с 3 знаками после запятой.

Решения
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        // Задание 1
        int num1 = 10;
        int num2 = 20;
        int sum = num1 + num2;
        Console.WriteLine($"Задание 1: {num1} + {num2} = {sum}");

        // Задание 2
        double num3 = 5.5;
        double num4 = 2.2;
        double product = num3 * num4;
        Console.WriteLine($"Задание 2: {num3} * {num4} = {product}");

        // Задание 3
        string str1 = "Hello";
        string str2 = "World";
        string concatStr = str1 + " " + str2;
        Console.WriteLine($"Задание 3: {concatStr}");

        // Задание 4
        string numStr = "123";
        int parsedNum = int.Parse(numStr);
        Console.WriteLine($"Задание 4: Преобразованная строка '{numStr}' в число = {parsedNum}");

        // Задание 5
        bool isHappy = true;
        Console.WriteLine($"Задание 5: Значение переменной isHappy = {isHappy}");

        // Задание 6
        char letter = 'A';
        string combinedStr = letter.ToString() + "pple";
        Console.WriteLine($"Задание 6: Объединённая строка = {combinedStr}");

        // Задание 7
        decimal price = 100.0M;
        decimal increasedPrice = price * 1.10M;
        Console.WriteLine($"Задание 7: Цена с увеличением на 10% = {increasedPrice}");

        // Задание 8
        int[] numbers = { 1, 2, 3, 4, 5 };
        int sumArray = numbers.Sum();
        Console.WriteLine($"Задание 8: Сумма элементов массива = {sumArray}");

        // Задание 9
        DateTime currentDate = DateTime.Now;
        DateTime futureDate = currentDate.AddDays(10);
        Console.WriteLine($"Задание 9: Текущая дата = {currentDate}, дата через 10 дней = {futureDate}");

        // Задание 10
        float num5 = 10.5F;
        float num6 = 3.2F;
        float divisionResult = num5 / num6;
        Console.WriteLine($"Задание 10: {num5} / {num6} = {divisionResult:F3}");
    }
}

Объяснение кода

  • Задание 1: Создаются две целочисленные переменные num1 и num2, затем они складываются, и результат выводится на экран.
  • Задание 2: Создаются две переменные типа double, перемножаются и выводятся.
  • Задание 3: Создаются две строки, объединяются с помощью оператора +, и результат выводится.
  • Задание 4: Строка "123" преобразуется в целое число с помощью метода int.Parse, и результат выводится.
  • Задание 5: Создается переменная типа bool, и её значение выводится.
  • Задание 6: Символ преобразуется в строку с помощью метода ToString, объединяется с другой строкой, и результат выводится.
  • Задание 7: Переменная типа decimal увеличивается на 10% и выводится.
  • Задание 8: Создается массив целых чисел, вычисляется сумма его элементов с помощью метода Sum, и результат выводится.
  • Задание 9: К текущей дате прибавляется 10 дней с использованием метода AddDays, и новая дата выводится.
  • Задание 10: Две переменные типа float делятся друг на друга, и результат выводится с точностью до трех знаков после запятой.

Написание консольных приложений на C#

Консольные приложения являются текстовыми программами, которые запускаются через командную строку. Они обычно выполняют простые задачи, такие как компиляция файла или шифрование части конфигурационного файла. Также консольные приложения могут принимать аргументы, чтобы контролировать своё поведение. Например, вы можете создать новое консольное приложение на языке F# с указанным именем, используя следующую команду:

dotnet new console -lang "F#" --name "ExploringConsole"

Вывод данных пользователю

Две наиболее распространенные задачи, которые выполняет консольное приложение, — это запись и чтение данных. Мы уже использовали метод WriteLine для вывода данных, но если нам не нужен символ перевода строки в конце, мы можем использовать метод Write.

Форматирование с использованием пронумерованных позиционных аргументов

Один из способов создания форматированных строк — использование пронумерованных позиционных аргументов. Эта функция поддерживается методами Write и WriteLine, а также методом Format класса string для методов, которые не поддерживают эту функцию.

Давайте начнем форматирование:

  1. Добавьте новый проект консольного приложения с именем Formatting в папку и рабочее пространство Chapter02.
  2. В методе Main добавьте операторы для объявления некоторых числовых переменных и их вывода в консоль, как показано в следующем коде:
    using System;
    
    namespace Formatting
    {
        class Program
        {
            static void Main(string[] args)
            {
                int numberOfApples = 12;
                decimal pricePerApple = 0.35M;
                Console.WriteLine(
                    format: "{0} apples costs {1:C}",
                    arg0: numberOfApples,
                    arg1: pricePerApple * numberOfApples);
                string formatted = string.Format(
                    format: "{0} apples costs {1:C}",
                    arg0: numberOfApples,
                    arg1: pricePerApple * numberOfApples);
                // WriteToFile(formatted); // пишет строку в файл
            }
        }
    }
    

Метод WriteToFile — это несуществующий метод, используемый для иллюстрации идеи.

Форматирование с использованием интерполированных строк

C# 6.0 и более поздние версии имеют удобную функцию под названием интерполированные строки. Строка, начинающаяся с $, может использовать фигурные скобки вокруг имени переменной или выражения, чтобы вывести текущее значение этой переменной или выражения в этом месте строки.

  1. В методе Main добавьте оператор внизу метода Main, как показано в следующем коде:
    Console.WriteLine($"{numberOfApples} apples costs {pricePerApple * numberOfApples:C}");
    
  2. Запустите консольное приложение и посмотрите результат, который будет следующим:
    12 apples costs $4.20
    

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

using System;

namespace Formatting
{
    class Program
    {
        static void Main(string[] args)
        {
            int numberOfApples = 12;
            decimal pricePerApple = 0.35M;

            // Использование пронумерованных позиционных аргументов
            Console.WriteLine(
                format: "{0} apples costs {1:C}",
                arg0: numberOfApples,
                arg1: pricePerApple * numberOfApples);

            string formatted = string.Format(
                format: "{0} apples costs {1:C}",
                arg0: numberOfApples,
                arg1: pricePerApple * numberOfApples);
            // WriteToFile(formatted); // пишет строку в файл

            // Использование интерполированных строк
            Console.WriteLine($"{numberOfApples} apples costs {pricePerApple * numberOfApples:C}");
        }
    }
}

Форматирование значений в строках

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

Полный синтаксис элемента форматирования выглядит так:

{ index [, alignment ] [ : formatString ] }

Каждый элемент форматирования может иметь выравнивание, что полезно при выводе таблиц значений, некоторые из которых могут нуждаться в выравнивании по левому или правому краю в пределах ширины символов. Значения выравнивания являются целыми числами. Положительные числа выравнивают по правому краю, а отрицательные — по левому.

Например, чтобы вывести таблицу с фруктами и количеством каждого из них, мы можем выровнять имена в пределах столбца шириной 8 символов по левому краю и отформатировать количество, как числа с нулем десятичных знаков, выровненные по правому краю в пределах столбца шириной 6 символов.

  1. В методе Main введите следующие операторы внизу:
    using System;
    
    namespace Formatting
    {
        class Program
        {
            static void Main(string[] args)
            {
                string applesText = "Apples";
                int applesCount = 1234;
                string bananasText = "Bananas";
                int bananasCount = 56789;
    
                Console.WriteLine(
                    format: "{0,-8} {1,6:N0}",
                    arg0: "Name",
                    arg1: "Count");
                Console.WriteLine(
                    format: "{0,-8} {1,6:N0}",
                    arg0: applesText,
                    arg1: applesCount);
                Console.WriteLine(
                    format: "{0,-8} {1,6:N0}",
                    arg0: bananasText,
                    arg1: bananasCount);
            }
        }
    }
    
  2. Запустите консольное приложение и обратите внимание на эффект выравнивания и форматирования чисел, как показано в следующем выводе:
    Name       Count
    Apples     1,234
    Bananas   56,789
    

Дополнительная информация

Вы можете прочитать больше подробностей о форматировании типов в .NET по следующей ссылке: Formatting Types in .NET

Получение текстового ввода от пользователя

Мы можем получить текстовый ввод от пользователя с помощью метода ReadLine. Этот метод ожидает, пока пользователь введет текст, и, как только пользователь нажмет Enter, возвращает введенный текст в виде строкового значения.

  1. В методе Main введите операторы, чтобы попросить пользователя ввести свое имя и возраст, а затем вывести введенные данные, как показано в следующем коде:
    using System;
    
    namespace UserInput
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Просим пользователя ввести свое имя
                Console.Write("Введите ваше имя и нажмите ENTER: ");
                string firstName = Console.ReadLine();
                
                // Просим пользователя ввести свой возраст
                Console.Write("Введите ваш возраст и нажмите ENTER: ");
                string age = Console.ReadLine();
                
                // Выводим приветственное сообщение с введенными данными
                Console.WriteLine($"Здравствуйте, {firstName}, вы отлично выглядите для своих {age} лет.");
            }
        }
    }
    
  2. Запустите консольное приложение.
  3. Введите имя и возраст, как показано в следующем примере вывода:
    Введите ваше имя и нажмите ENTER: Игорь
    Введите ваш возраст и нажмите ENTER: 29
    Здравствуйте, Игорь, вы отлично выглядите для своих 29 лет.
    

Пояснение кода

  • Console.Write(«Введите ваше имя и нажмите ENTER: «); — выводит сообщение с просьбой ввести имя.
  • string firstName = Console.ReadLine(); — читает введенное пользователем имя и сохраняет его в переменную firstName.
  • Console.Write(«Введите ваш возраст и нажмите ENTER: «); — выводит сообщение с просьбой ввести возраст.
  • string age = Console.ReadLine(); — читает введенный пользователем возраст и сохраняет его в переменную age.
  • Console.WriteLine($»Здравствуйте, {firstName}, вы отлично выглядите для своих {age} лет.»); — выводит приветственное сообщение, используя введенные имя и возраст.

Импортирование пространства имен

Вы могли заметить, что в отличие от нашего самого первого приложения, .NET!, мы не вводили System перед Console. Это потому, что System является пространством имен, которое можно сравнить с адресом для типа. Чтобы точно указать на кого-то, вы могли бы использовать Oxford.HighStreet.BobSmith, что говорит нам искать человека по имени Bob Smith на High Street в городе Oxford.

Строка System.Console.WriteLine говорит компилятору искать метод WriteLine в типе Console в пространстве имен System. Чтобы упростить наш код, команда dotnet new console добавила оператор в начало файла кода, чтобы компилятор всегда искал в пространстве имен System типы, которые не имеют префикса пространства имен, как показано в следующем коде:

using System;

Мы называем это импортированием пространства имен. Эффект от импортирования пространства имен заключается в том, что все доступные типы в этом пространстве имен будут доступны вашей программе без необходимости ввода префикса пространства имен и будут видны в IntelliSense во время написания кода.

Упрощение использования консоли

В C# 6.0 и позднее, оператор using можно использовать для дальнейшего упрощения нашего кода. Тогда нам не нужно будет вводить тип Console во всем нашем коде. Мы можем использовать функцию замены Visual Studio Code, чтобы удалить те места, где мы ранее писали Console.:

  1. Добавьте оператор для статического импортирования класса System.Console в начало файла Program.cs, как показано в следующем коде:
    using static System.Console;
    
  2.  Выберите первое Console. в вашем коде, убедившись, что вы также выбрали точку после слова Console.
  3. Перейдите в меню Edit | Replace и обратите внимание, что появится диалоговое окно, готовое для ввода того, что вы хотите заменить Console. на пустое значение
  4. Нажмите кнопку Replace All (вторая из двух кнопок справа от поля замены) или нажмите Alt + A или Alt + Cmd + Enter, чтобы заменить все, а затем закройте окно замены, нажав на крестик в его верхнем правом углу.
    using static System.Console;
    
    namespace UserInput
    {
        class Program
        {
            static void Main(string[] args)
            {
                Write("Введите ваше имя и нажмите ENTER: ");
                string firstName = ReadLine();
    
                Write("Введите ваш возраст и нажмите ENTER: ");
                string age = ReadLine();
    
                WriteLine($"Здравствуйте, {firstName}, вы отлично выглядите для своих {age} лет.");
            }
        }
    }
    

Получение ввода с клавиатуры от пользователя

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

  1. В методе Main введите операторы, чтобы попросить пользователя нажать любую комбинацию клавиш, а затем вывести информацию об этом, как показано в следующем коде:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            Write("Нажмите любую комбинацию клавиш: ");
            ConsoleKeyInfo key = ReadKey();
            WriteLine();
            WriteLine("Клавиша: {0}, Символ: {1}, Модификаторы: {2}",
                arg0: key.Key,
                arg1: key.KeyChar,
                arg2: key.Modifiers);
        }
    }
    
  2. Запустите консольное приложение, нажмите клавишу K и обратите внимание на результат, как показано в следующем выводе:
    Нажмите любую комбинацию клавиш: k
    Клавиша: K, Символ: k, Модификаторы: 0
    
  3. Запустите консольное приложение, удерживая клавишу Shift и нажав клавишу K, и обратите внимание на результат:
    Нажмите любую комбинацию клавиш: K
    Клавиша: K, Символ: K, Модификаторы: Shift
    
  4. Запустите консольное приложение, нажмите клавишу F12 и обратите внимание на результат:
    Нажмите любую комбинацию клавиш:
    Клавиша: F12, Символ: , Модификаторы: 0
    

При запуске консольного приложения в Терминале в Visual Studio Code, некоторые комбинации клавиш могут быть перехвачены редактором кода или операционной системой до того, как они будут обработаны вашим приложением.

Получение аргументов

Вы могли задуматься, что такое аргументы string[] args в методе Main. Это массив, используемый для передачи аргументов в консольное приложение. Давайте рассмотрим, как это работает.

Аргументы командной строки разделяются пробелами. Другие символы, такие как дефисы и двоеточия, считаются частью значения аргумента. Чтобы включить пробелы в значение аргумента, заключите значение аргумента в одинарные или двойные кавычки.

Представим, что мы хотим ввести имена некоторых цветов для переднего и заднего плана, а также размеры окна Терминала через командную строку. Мы можем прочитать цвета и числа, считав их из массива args, который всегда передается в метод Main консольного приложения.

  1. Создайте новую папку для консольного приложения с именем Arguments и добавьте ее в рабочую область Chapter02.
  2. Добавьте оператор для статического импортирования типа System.Console и оператор для вывода количества аргументов, переданных приложению, как показано в следующем коде:
    using static System.Console;
    
    namespace Arguments
    {
        class Program
        {
            static void Main(string[] args)
            {
                WriteLine($"Количество аргументов: {args.Length}");
            }
        }
    }
    

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

  3. Запустите консольное приложение и посмотрите на результат:
    Количество аргументов: 0
    
  4. В Терминале введите несколько аргументов после команды dotnet run, как показано в следующей командной строке:
    dotnet run firstarg second-arg third:arg "fourth arg"
    
  5. Обратите внимание на результат, который указывает на наличие четырех аргументов:
    Количество аргументов: 4
    
  6. Чтобы перебирать значения этих четырех аргументов, добавьте следующие операторы после вывода длины массива:
    foreach (string arg in args)
    {
        WriteLine(arg);
    }
    
  7. В Терминале повторите те же аргументы после команды dotnet run:
    dotnet run firstarg second-arg third:arg "fourth arg"
    
  8. Обратите внимание на результат, который показывает детали четырех аргументов:
    Количество аргументов: 4
    firstarg
    second-arg
    third:arg
    fourth arg
    

Установка параметров с помощью аргументов

Теперь мы используем эти аргументы, чтобы позволить пользователю выбрать цвет фона, цвет текста и размер курсора окна вывода. Размер курсора может быть целым числом от 1, что означает линию внизу ячейки курсора, до 100, что означает процент от высоты ячейки курсора.

Пространство имен System уже импортировано, поэтому компилятор знает о типах ConsoleColor и Enum. Если вы не видите эти типы в списке IntelliSense, это потому, что у вас отсутствует оператор using System; в начале файла.

  1. Добавьте операторы, чтобы предупредить пользователя, если он не ввел три аргумента, а затем проанализируйте эти аргументы и используйте их для установки цвета и размеров окна консоли, как показано в следующем коде:
    using System;
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 3)
            {
                WriteLine("Вы должны указать два цвета и размер курсора, например:");
                WriteLine("dotnet run red yellow 50");
                return; // остановить выполнение
            }
    
            ForegroundColor = (ConsoleColor)Enum.Parse(
                enumType: typeof(ConsoleColor),
                value: args[0],
                ignoreCase: true);
            BackgroundColor = (ConsoleColor)Enum.Parse(
                enumType: typeof(ConsoleColor),
                value: args[1],
                ignoreCase: true);
            
            try
            {
                CursorSize = int.Parse(args[2]);
            }
            catch (PlatformNotSupportedException)
            {
                WriteLine("Текущая платформа не поддерживает изменение размера курсора.");
            }
        }
    }
    
  2. Введите следующую команду в терминале:
    dotnet run red yellow 50
    

На Linux этот код будет работать корректно. На Windows запустится, но размер курсора не изменится. На macOS вы увидите необработанное исключение.

Обработка платформ, которые не поддерживают API

Как решить эту проблему? Мы можем решить ее с помощью обработчика исключений. Просто введите следующий код:

  1. Измените код, чтобы обернуть строки, изменяющие размер курсора, в блок try, как показано в следующем коде:
    try
    {
        CursorSize = int.Parse(args[2]);
    }
    catch (PlatformNotSupportedException)
    {
        WriteLine("Текущая платформа не поддерживает изменение размера курсора.");
    }
    
  2. Повторно запустите консольное приложение и обратите внимание, что исключение поймано и пользователю показано более дружелюбное сообщение.

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

if (OperatingSystem.IsWindows())
{
    // выполнить код, который работает только на Windows
}

Класс OperatingSystem имеет эквивалентные методы для других распространенных ОС, таких как Android, iOS, Linux, macOS и даже браузер, что полезно для компонентов веб-приложений на Blazor.

Закрепление материала

Проверьте свои знания

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

Какой тип данных вы выберете для следующих «чисел»?

  1. Номер телефона человека
  2. Рост человека
  3. Возраст человека
  4. Зарплата человека
  5. ISBN книги
  6. Цена книги
  7. Вес доставки книги
  8. Население страны
  9. Количество звезд во Вселенной
  10. Количество сотрудников в малых и средних предприятиях в Соединенном Королевстве (до примерно 50,000 сотрудников на предприятие)

Практика размеров и диапазонов чисел

Создайте консольное приложение с именем Exercise02, которое выводит количество байтов в памяти для каждого из следующих типов чисел, а также минимальные и максимальные значения, которые они могут иметь: sbyte, byte, short, ushort, int, uint, long, ulong, float, double и decimal.

Дополнительная информация: Вы всегда можете прочитать документацию по композитному форматированию для того, чтобы научиться выравнивать текст в консольном приложении. Документация доступна по ссылке: Composite Formatting.

Исследуйте темы

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

Задания на создание консольных приложений в C#

Задание 11: Калькулятор с основными операциями

Создайте консольное приложение, которое запрашивает у пользователя два числа и операцию (сложение, вычитание, умножение, деление) и выводит результат.

Задание 12: Проверка на палиндром

Напишите консольное приложение, которое принимает строку от пользователя и определяет, является ли она палиндромом.

Задание 13: Конвертер температур

Создайте консольное приложение, которое запрашивает у пользователя температуру в градусах Цельсия и конвертирует ее в градусы Фаренгейта и Кельвина.

Задание 14: Подсчет гласных и согласных

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

Задание 15: Игра «Угадай число»

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

Задание 16: Таблица умножения

Напишите консольное приложение, которое выводит таблицу умножения для чисел от 1 до 10.

Задание 17: Калькулятор Body Mass Index (BMI)

Создайте консольное приложение, которое запрашивает у пользователя вес (в килограммах) и рост (в метрах), вычисляет BMI и сообщает, является ли этот индекс нормальным, недостаточным или избыточным.

Задание 18: Подсчет слов

Напишите консольное приложение, которое принимает строку от пользователя и выводит количество слов в этой строке.

Задание 19: Конвертер валют

Создайте консольное приложение, которое запрашивает у пользователя сумму в одной валюте (например, доллары США) и конвертирует ее в другую валюту (например, евро) по заданному курсу.

Задание 20: Управление списком задач (To-Do List)

Создайте консольное приложение, которое позволяет пользователю добавлять, удалять и просматривать задачи в списке дел.

Решения
using System;

namespace ConsoleApplications
{
    class Program
    {
        static void Main(string[] args)
        {
            // Вызываем метод для каждого задания
            // Task11_Calculator();
            // Task12_PalindromeChecker();
            // Task13_TemperatureConverter();
            // Task14_VowelConsonantCounter();
            // Task15_NumberGuessingGame();
            // Task16_MultiplicationTable();
            // Task17_BMICalculator();
            // Task18_WordCounter();
            // Task19_CurrencyConverter();
            // Task20_ToDoList();
        }

        // Задание 11: Калькулятор с основными операциями
        static void Task11_Calculator()
        {
            Console.Write("Введите первое число: ");
            double num1 = Convert.ToDouble(Console.ReadLine());

            Console.Write("Введите второе число: ");
            double num2 = Convert.ToDouble(Console.ReadLine());

            Console.Write("Введите операцию (+, -, *, /): ");
            char operation = Console.ReadLine()[0];

            double result = 0;

            switch (operation)
            {
                case '+':
                    result = num1 + num2;
                    break;
                case '-':
                    result = num1 - num2;
                    break;
                case '*':
                    result = num1 * num2;
                    break;
                case '/':
                    result = num1 / num2;
                    break;
                default:
                    Console.WriteLine("Неверная операция");
                    return;
            }

            Console.WriteLine($"Результат: {result}");
        }

        // Задание 12: Проверка на палиндром
        static void Task12_PalindromeChecker()
        {
            Console.Write("Введите строку: ");
            string input = Console.ReadLine();
            string reversed = new string(input.ToCharArray().Reverse().ToArray());

            if (input.Equals(reversed, StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine("Это палиндром.");
            }
            else
            {
                Console.WriteLine("Это не палиндром.");
            }
        }

        // Задание 13: Конвертер температур
        static void Task13_TemperatureConverter()
        {
            Console.Write("Введите температуру в градусах Цельсия: ");
            double celsius = Convert.ToDouble(Console.ReadLine());

            double fahrenheit = (celsius * 9 / 5) + 32;
            double kelvin = celsius + 273.15;

            Console.WriteLine($"Температура в Фаренгейтах: {fahrenheit}");
            Console.WriteLine($"Температура в Кельвинах: {kelvin}");
        }

        // Задание 14: Подсчет гласных и согласных
        static void Task14_VowelConsonantCounter()
        {
            Console.Write("Введите строку: ");
            string input = Console.ReadLine().ToLower();

            int vowelCount = 0, consonantCount = 0;

            foreach (char c in input)
            {
                if ("aeiou".Contains(c))
                {
                    vowelCount++;
                }
                else if (char.IsLetter(c))
                {
                    consonantCount++;
                }
            }

            Console.WriteLine($"Гласные: {vowelCount}, Согласные: {consonantCount}");
        }

        // Задание 15: Игра "Угадай число"
        static void Task15_NumberGuessingGame()
        {
            Random rand = new Random();
            int numberToGuess = rand.Next(1, 101);
            int userGuess = 0;

            Console.WriteLine("Угадайте число от 1 до 100.");

            while (userGuess != numberToGuess)
            {
                Console.Write("Введите ваше предположение: ");
                userGuess = Convert.ToInt32(Console.ReadLine());

                if (userGuess < numberToGuess)
                {
                    Console.WriteLine("Слишком мало!");
                }
                else if (userGuess > numberToGuess)
                {
                    Console.WriteLine("Слишком много!");
                }
                else
                {
                    Console.WriteLine("Поздравляем! Вы угадали число.");
                }
            }
        }

        // Задание 16: Таблица умножения
        static void Task16_MultiplicationTable()
        {
            for (int i = 1; i <= 10; i++)
            {
                for (int j = 1; j <= 10; j++)
                {
                    Console.Write($"{i * j,4}");
                }
                Console.WriteLine();
            }
        }

        // Задание 17: Калькулятор BMI
        static void Task17_BMICalculator()
        {
            Console.Write("Введите вес (в кг): ");
            double weight = Convert.ToDouble(Console.ReadLine());

            Console.Write("Введите рост (в метрах): ");
            double height = Convert.ToDouble(Console.ReadLine());

            double bmi = weight / (height * height);

            Console.WriteLine($"Ваш BMI: {bmi:F2}");

            if (bmi < 18.5)
            {
                Console.WriteLine("Недостаточный вес.");
            }
            else if (bmi < 24.9)
            {
                Console.WriteLine("Нормальный вес.");
            }
            else
            {
                Console.WriteLine("Избыточный вес.");
            }
        }

        // Задание 18: Подсчет слов
        static void Task18_WordCounter()
        {
            Console.Write("Введите строку: ");
            string input = Console.ReadLine();

            string[] words = input.Split(new char[] { ' ', '\t', '\n' }, StringSplitOptions.RemoveEmptyEntries);

            Console.WriteLine($"Количество слов: {words.Length}");
        }

        // Задание 19: Конвертер валют
        static void Task19_CurrencyConverter()
        {
            Console.Write("Введите сумму в долларах США: ");
            double usd = Convert.ToDouble(Console.ReadLine());

            const double usdToEurRate = 0.85; // Пример курса обмена

            double eur = usd * usdToEurRate;

            Console.WriteLine($"Сумма в евро: {eur:F2}");
        }

        // Задание 20: Управление списком задач (To-Do List)
        static void Task20_ToDoList()
        {
            List<string> tasks = new List<string>();
            string command = "";

            while (command != "exit")
            {
                Console.WriteLine("Введите команду (add, remove, list, exit): ");
                command = Console.ReadLine().ToLower();

                switch (command)
                {
                    case "add":
                        Console.Write("Введите задачу: ");
                        tasks.Add(Console.ReadLine());
                        break;
                    case "remove":
                        Console.Write("Введите номер задачи для удаления: ");
                        int index = Convert.ToInt32(Console.ReadLine());
                        if (index >= 0 && index < tasks.Count)
                        {
                            tasks.RemoveAt(index);
                        }
                        else
                        {
                            Console.WriteLine("Неверный номер задачи.");
                        }
                        break;
                    case "list":
                        Console.WriteLine("Список задач:");
                        for (int i = 0; i < tasks.Count; i++)
                        {
                            Console.WriteLine($"{i}: {tasks[i]}");
                        }
                        break;
                }
            }
        }
    }
}

Объяснение кода

  • Задание 11: Калькулятор запрашивает два числа и операцию, выполняет операцию и выводит результат.
  • Задание 12: Проверка на палиндром принимает строку и проверяет, совпадает ли она с перевернутой версией.
  • Задание 13: Конвертер температур переводит значение из Цельсия в Фаренгейты и Кельвины.
  • Задание 14: Подсчет гласных и согласных определяет количество каждой категории в введенной строке.
  • Задание 15: Игра «Угадай число» генерирует случайное число и просит пользователя угадать его, давая подсказки.
  • Задание 16: Таблица умножения выводит таблицу умножения от 1 до 10.
  • Задание 17: Калькулятор BMI рассчитывает индекс массы тела и оценивает его.
  • Задание 18: Подсчет слов разбивает строку на слова и считает их количество.
  • Задание 19: Конвертер валют переводит сумму из долларов США в евро по фиксированному курсу.
  • Задание 20: Список задач позволяет добавлять, удалять и просматривать задачи в списке.
Понравилась статья? Поделиться с друзьями:
Школа Виктора Комлева
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.