Перейти к основному содержимому

Подробнее о переменных в C#.

Введение

Краткий обзор того, что будет рассмотрено в лекции

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

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

Сложные типы переменных:

  • Поговорим о массивах, списках, структурах и классах — как с ними работать и в каких ситуациях они применяются.
  • Разберём основные методы и подходы для работы со сложными типами данных.

Работа со строками:

  • Изучим основные операции со строками, включая конкатенацию, форматирование и сравнение.
  • Обратим внимание на методы класса String и эффективную работу с текстовыми данными.

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

1.1. Введение в типы данных

Основные типы данных в C#

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

Значимые типы включают:

  1. Целочисленные типы:

    • byte (8 бит, от 0 до 255)
    • sbyte (8 бит, от -128 до 127)
    • short (16 бит, от -32 768 до 32 767)
    • ushort (16 бит, от 0 до 65 535)
    • int (32 бита, от -2 147 483 648 до 2 147 483 647)
    • uint (32 бита, от 0 до 4 294 967 295)
    • long (64 бита, от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807)
    • ulong (64 бита, от 0 до 18 446 744 073 709 551 615)
  2. Вещественные типы:

    • float (32 бита, приблизительно 7 значащих цифр)
    • double (64 бита, приблизительно 15-16 значащих цифр)
    • decimal (128 бит, точность до 28-29 значащих цифр; используется для финансовых расчетов)
  3. Символьный тип:

    • char (16 бит, символ в кодировке Unicode)
  4. Логический тип:

    • bool (1 бит, принимает значения true или false)

Ссылочные типы включают:

  1. Строки:
    • string (строковый тип, представляющий последовательность символов)
  2. Объекты:
    • object (базовый тип для всех других типов, как значимых, так и ссылочных)

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

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

В языке C# преобразование типов — это процесс изменения значения переменной из одного типа данных в другой. Существуют два основных способа преобразования типов: неявное (implicit) и явное (explicit).

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

Неявное преобразование (implicit conversion) — это автоматическое преобразование типов, которое выполняется компилятором C# без необходимости явного указания разработчиком. Оно возможно, если преобразование безопасно и не приведет к потере данных. Обычно неявное преобразование выполняется при преобразовании типов меньшего размера или диапазона в типы большего размера или диапазона.

Примеры неявного преобразования:

  1. Преобразование от int к long:
int a = 123;
long b = a; // Неявное преобразование int к long

В данном примере значение переменной a типа int автоматически преобразуется в тип long, поскольку long имеет больший диапазон значений, и преобразование безопасно.

  1. Преобразование от float к double:
float x = 9.8f;
double y = x; // Неявное преобразование float к double

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

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

Явное преобразование (explicit conversion) — это преобразование типов, которое требует явного указания разработчиком, поскольку такое преобразование может быть небезопасным и привести к потере данных. В C# для явного преобразования используется оператор приведения (тип).

Примеры явного преобразования:

  1. Преобразование от double к int:
double pi = 3.14159;
int integerPart = (int)pi; // Явное преобразование double к int

В данном примере значение переменной pi типа double явно приводится к типу int. В результате теряется дробная часть числа, и в переменной integerPart останется только целая часть числа pi, равная 3.

  1. Преобразование от long к short:
long largeNumber = 30000;
short smallNumber = (short)largeNumber; // Явное преобразование long к short

Здесь выполняется явное преобразование от long к short. Поскольку тип short имеет меньший диапазон, возможны ситуации, когда значение переменной largeNumber не может быть корректно преобразовано в short, что приведет к ошибке переполнения.

Заключение по введению в типы данных и преобразованиям

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

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

Объяснение, что такое неявное преобразование (implicit conversion)

Неявное преобразование (implicit conversion) в языке программирования C# представляет собой автоматическое преобразование значений одного типа данных в другой тип, осуществляемое компилятором. Такой тип преобразования возможен тогда, когда не возникает риска потери данных или возникновения исключений, а также когда оба типа совместимы по своей природе. Главная цель неявного преобразования — облегчить разработку, устранив необходимость явного указания преобразования и тем самым минимизируя потенциальные ошибки, связанные с типами данных.

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

Примеры неявного преобразования

Преобразование меньшего целочисленного типа к большему (int -> long)

Рассмотрим пример, где целочисленный тип int преобразуется в целочисленный тип long. Тип int в C# занимает 32 бита и может хранить значения в диапазоне от -2 147 483 648 до 2 147 483 647. Тип long, в свою очередь, занимает 64 бита и может хранить значения в диапазоне от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807.

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

Пример:

int number = 2147483647;
long largerNumber = number; // Неявное преобразование int к long

В данном примере значение переменной number типа int присваивается переменной largerNumber типа long. Поскольку тип long имеет более широкий диапазон, компилятор выполняет преобразование автоматически, без необходимости явного указания. После преобразования переменная largerNumber содержит то же значение, что и переменная number, но уже в формате типа long.

Важно отметить, что неявное преобразование здесь возможно благодаря тому, что диапазон значений int полностью укладывается в диапазон значений long. Даже если бы значение number было минимальным или максимальным возможным значением для int, оно все равно могло бы быть корректно преобразовано в long.

Преобразование от вещественного типа к более широкому (float -> double)

Вещественные типы данных в C# используются для представления чисел с плавающей точкой. Тип float занимает 32 бита и имеет точность приблизительно до 7 значащих цифр. Тип double занимает 64 бита и обеспечивает значительно большую точность — приблизительно до 15-16 значащих цифр.

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

Пример:

float piApproximation = 3.14f;
double precisePi = piApproximation; // Неявное преобразование float к double

В этом примере значение переменной piApproximation типа float присваивается переменной precisePi типа double. Процесс неявного преобразования позволяет переменной precisePi хранить то же значение, но уже с возможностью большей точности, хотя в данном конкретном случае исходное значение не требует дополнительной точности.

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

Выводы по разделу

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

1.3. Явное преобразование типов

Объяснение, что такое явное преобразование (explicit conversion)

Явное преобразование (explicit conversion) в языке C# представляет собой процесс преобразования значений одного типа данных в другой, который требует явного указания разработчиком. Это необходимо в тех случаях, когда преобразование может привести к потере данных, изменению представления или другим нежелательным последствиям, которые компилятор не может предсказать или предотвратить автоматически. Поскольку такие преобразования могут быть потенциально небезопасными, компилятор требует явного указания на необходимость преобразования, чтобы разработчик осознанно управлял этим процессом.

Явное преобразование применяется, когда:

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

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

Использование оператора приведения (тип)

В C# для выполнения явного преобразования используется оператор приведения (тип), где тип — это целевой тип данных, к которому необходимо преобразовать значение. Оператор приведения указывает компилятору на необходимость выполнения преобразования, даже если это может привести к утрате данных или другим последствиям.

Синтаксис:

целевой_тип переменная_целевого_типа = (целевой_тип) переменная_исходного_типа;

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

Примеры явного преобразования

Преобразование от double к int

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

Пример:

double originalValue = 9.99;
int integerValue = (int)originalValue; // Явное преобразование double к int

В данном примере значение переменной originalValue типа double равно 9.99. При явном преобразовании в тип int дробная часть (0.99) отбрасывается, и в переменной integerValue остается только целая часть числа, равная 9.

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

Преобразование от string к int с использованием Convert и Parse

Преобразование строкового представления числа в числовой тип (int) является типичным примером явного преобразования, требующим использования методов для корректного выполнения преобразования. В C# для этого предусмотрены два основных способа: использование метода Convert.ToInt32 и метода int.Parse.

1. Использование Convert.ToInt32:

Метод Convert.ToInt32 преобразует строковое значение в целое число типа int. Этот метод также способен обрабатывать значения null, возвращая 0, если строка является null.

Пример:

string numberString = "123";
int number = Convert.ToInt32(numberString); // Преобразование string к int с использованием Convert

В данном примере строка "123" успешно преобразуется в целое число 123 с использованием метода Convert.ToInt32.

Однако, если строка не может быть интерпретирована как число (например, "abc"), метод Convert.ToInt32 выбросит исключение FormatException.

2. Использование int.Parse:

Метод int.Parse также выполняет преобразование строки в целое число, однако он более строг в обработке значений null и некорректных строк. Если строка не может быть преобразована, метод выбрасывает исключение FormatException. Если строка равна null, выбрасывается исключение ArgumentNullException.

Пример:

string numberString = "456";
int number = int.Parse(numberString); // Преобразование string к int с использованием Parse

Здесь строка "456" успешно преобразуется в целое число 456 с использованием метода int.Parse.

Для повышения безопасности в случаях, когда строка может быть некорректной, рекомендуется использовать метод int.TryParse, который не выбрасывает исключений, а возвращает false в случае неудачного преобразования, позволяя программе продолжать работу без сбоев.

Пример с int.TryParse:

string numberString = "789";
bool success = int.TryParse(numberString, out int number); // Безопасное преобразование string к int
if (success)
{
// Успешное преобразование
}
else
{
// Не удалось преобразовать
}

В этом примере метод int.TryParse возвращает true, если преобразование прошло успешно, и false — в противном случае. Если преобразование удалось, значение присваивается переменной number.

Выводы по разделу

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

1.4. Использование класса Convert

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

Методы класса Convert

Convert.ToInt32

Метод Convert.ToInt32 используется для преобразования различных типов данных, таких как bool, byte, char, decimal, double, float, int, long, sbyte, short, string, uint, ulong, ushort, в целое число типа int. Этот метод пытается преобразовать значение в int, если это возможно.

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

string numberString = "123";
int number = Convert.ToInt32(numberString); // Преобразование строки в int

double floatingNumber = 123.45;
int roundedNumber = Convert.ToInt32(floatingNumber); // Преобразование double в int, округление

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

Convert.ToDouble

Метод Convert.ToDouble преобразует переданное значение в число с плавающей точкой двойной точности (double). Этот метод может принимать такие типы данных, как bool, byte, char, decimal, float, int, long, sbyte, short, string, uint, ulong, ushort.

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

string numberString = "123.45";
double number = Convert.ToDouble(numberString); // Преобразование строки в double

int integerValue = 100;
double doubleValue = Convert.ToDouble(integerValue); // Преобразование int в double

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

Convert.ToString

Метод Convert.ToString преобразует переданное значение в строку (string). Этот метод принимает различные типы данных и возвращает их строковое представление. Практически каждый тип данных может быть преобразован в строку, что делает этот метод универсальным инструментом для генерации текстового вывода.

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

int number = 456;
string numberString = Convert.ToString(number); // Преобразование int в string

double floatingNumber = 789.01;
string floatingNumberString = Convert.ToString(floatingNumber); // Преобразование double в string

В этих примерах целое число 456 и вещественное число 789.01 преобразуются в строковые представления "456" и "789.01" соответственно.

Другие методы класса Convert

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

  • Convert.ToBoolean: Преобразует значение в логический тип bool. Например, Convert.ToBoolean(1) вернет true.
  • Convert.ToDecimal: Преобразует значение в десятичное число с высокой точностью (decimal). Например, Convert.ToDecimal(123.456) вернет 123.456M.
  • Convert.ToChar: Преобразует значение в символ char. Например, Convert.ToChar(65) вернет 'A'.
  • Convert.ToInt64: Преобразует значение в целое число типа long. Например, Convert.ToInt64("9876543210") вернет 9876543210L.

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

Примеры преобразования типов с помощью класса Convert

Рассмотрим несколько сценариев, в которых используется класс Convert для преобразования типов данных:

1. Преобразование bool в int:

bool isActive = true;
int activeValue = Convert.ToInt32(isActive); // Результат: 1

В данном примере значение типа bool true преобразуется в целое число 1. Если бы значение было false, результатом было бы 0.

2. Преобразование string в decimal:

string priceString = "1234.56";
decimal price = Convert.ToDecimal(priceString); // Результат: 1234.56M

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

3. Преобразование int в char:

int asciiCode = 65;
char character = Convert.ToChar(asciiCode); // Результат: 'A'

Целое число 65, соответствующее ASCII-коду символа A, преобразуется в символ 'A'.

Возможные ошибки при преобразовании и их обработка

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

  1. FormatException:

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

    Пример:

    try
    {
    string invalidNumberString = "abc";
    int number = Convert.ToInt32(invalidNumberString); // Ошибка: FormatException
    }
    catch (FormatException ex)
    {
    Console.WriteLine("Ошибка преобразования: неверный формат строки.");
    }
  2. OverflowException:

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

    Пример:

    try
    {
    long largeNumber = long.MaxValue;
    int smallerNumber = Convert.ToInt32(largeNumber); // Ошибка: OverflowException
    }
    catch (OverflowException ex)
    {
    Console.WriteLine("Ошибка преобразования: значение превышает допустимый диапазон.");
    }
  3. InvalidCastException:

    • Возникает, если преобразование между указанными типами невозможно.
    • Например, попытка преобразовать объект нечислового типа в число.

    Пример:

    try
    {
    object someObject = new object();
    int number = Convert.ToInt32(someObject); // Ошибка: InvalidCastException
    }
    catch (InvalidCastException ex)
    {
    Console.WriteLine("Ошибка преобразования: недопустимое преобразование типа.");
    }

Выводы по разделу

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

1.5. Преобразование с помощью метода Parse и TryParse

Разница между Parse и TryParse

Методы Parse и TryParse в C# предназначены для преобразования строк в соответствующие типы данных, такие как int, double, decimal и другие. Однако они отличаются по своему поведению при обработке некорректных данных.

  • Parse: Метод Parse предназначен для прямого преобразования строки в определенный тип данных. Если строка не может быть успешно преобразована (например, если она содержит нечисловые символы, пустая или имеет неподходящий формат), метод выбрасывает исключение FormatException. Если строка равна null, выбрасывается исключение ArgumentNullException. Parse является строгим методом и требует, чтобы строка была полностью корректной.

  • TryParse: Метод TryParse, в отличие от Parse, не выбрасывает исключений. Вместо этого он возвращает булево значение true или false, указывая на успешность преобразования. В случае неудачного преобразования возвращается false, и переменная-результат получает значение по умолчанию для данного типа (например, 0 для int). TryParse также не выбрасывает исключение при передаче null, возвращая false.

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

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

int.Parse и int.TryParse

Использование int.Parse:

Метод int.Parse преобразует строку, содержащую числовое значение, в целое число (int). В случае некорректного формата строки или если строка равна null, метод выбрасывает исключение.

Пример:

string validNumberString = "123";
int number = int.Parse(validNumberString); // Успешное преобразование, результат: 123

string invalidNumberString = "abc";
try
{
int invalidNumber = int.Parse(invalidNumberString); // Ошибка: FormatException
}
catch (FormatException ex)
{
Console.WriteLine("Ошибка: некорректный формат строки.");
}

В первом примере строка "123" успешно преобразуется в число 123. Во втором примере строка "abc" вызывает исключение FormatException, так как не может быть интерпретирована как целое число. Исключение обрабатывается в блоке catch, что предотвращает сбой программы.

Использование int.TryParse:

Метод int.TryParse выполняет ту же задачу, что и int.Parse, но с более гибким подходом к обработке ошибок. Вместо выброса исключения метод возвращает false в случае неудачного преобразования, что позволяет программе продолжать работу без сбоев.

Пример:

string validNumberString = "456";
bool success = int.TryParse(validNumberString, out int number); // Успешное преобразование
if (success)
{
Console.WriteLine($"Успешно преобразовано: {number}");
}
else
{
Console.WriteLine("Преобразование не удалось.");
}

string invalidNumberString = "def";
success = int.TryParse(invalidNumberString, out number); // Неудачное преобразование
if (success)
{
Console.WriteLine($"Успешно преобразовано: {number}");
}
else
{
Console.WriteLine("Преобразование не удалось.");
}

В этом примере первая строка "456" успешно преобразуется в число 456, и метод TryParse возвращает true, указывая на успешное преобразование. Во втором случае строка "def" не может быть преобразована в число, и TryParse возвращает false. Переменная number при этом получает значение по умолчанию для типа int (0), и программа информирует пользователя о неудаче преобразования, но не прерывает выполнение.

Как избежать ошибок с помощью TryParse

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

Основные преимущества TryParse:

  1. Безопасность: TryParse не выбрасывает исключений, что устраняет необходимость в блоках try-catch для обработки ошибок, связанных с некорректным вводом данных.
  2. Устойчивость программы: Даже при попытке преобразования некорректной строки программа продолжает работу, а не прерывается из-за выброса исключения.
  3. Простота использования: Метод возвращает true при успешном преобразовании и false — при неудачном, что позволяет легко проверять результат и принимать соответствующие действия.

Пример сценария использования TryParse:

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

Console.Write("Введите число: ");
string userInput = Console.ReadLine();

if (int.TryParse(userInput, out int parsedNumber))
{
Console.WriteLine($"Вы ввели число: {parsedNumber}");
// Дальнейшая обработка числа
}
else
{
Console.WriteLine("Некорректный ввод, пожалуйста, введите допустимое целое число.");
// Повторный запрос ввода
}

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

Выводы по разделу

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

2. Сложные типы переменных

2.1. Массивы

Понятие массива, его объявление и инициализация

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

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

Инициализация массива — это процесс создания и выделения памяти для массива с последующим заполнением его значениями по умолчанию или заданными пользователем.

Объявление массива

Объявление массива включает указание типа данных, который будет храниться в массиве, за которым следуют квадратные скобки [], указывающие на то, что переменная будет массивом.

Синтаксис:

тип[] имяМассива;

Пример объявления массива строк:

string[] names;

В данном примере переменная names объявляется как массив строк. Однако, на этом этапе массив еще не инициализирован, и его размер не определен.

Инициализация массива

Инициализация массива может быть выполнена несколькими способами:

  1. Инициализация с указанием размера:
int[] numbers = new int[5];

Этот пример создает массив numbers из 5 элементов типа int, которые инициализируются значениями по умолчанию для типа int (нулевыми значениями).

  1. Инициализация с заданием начальных значений:
int[] numbers = new int[] { 1, 2, 3, 4, 5 };

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

  1. Сокращенная форма инициализации:
int[] numbers = { 1, 2, 3, 4, 5 };

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

Примеры одномерных и многомерных массивов

Одномерные массивы

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

Пример одномерного массива:

string[] cities = { "New York", "Los Angeles", "Chicago" };

В этом примере массив cities содержит три строки, представляющие названия городов. Доступ к элементам осуществляется по индексу, начиная с 0.

Многомерные массивы

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

Пример двумерного массива:

int[,] matrix = new int[3, 3];

Этот пример создает двумерный массив matrix размером 3x3, который можно представить как таблицу из 3 строк и 3 столбцов.

Пример инициализации двумерного массива:

int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};

Этот пример создает и инициализирует двумерный массив matrix значениями от 1 до 9, организованными в таблицу размером 3x3.

Пример трехмерного массива:

int[,,] cube = new int[2, 2, 2];

Этот пример создает трехмерный массив cube, который можно представить как куб размером 2x2x2.

Доступ к элементам массива, циклы для работы с массивами

Доступ к элементам массива

Доступ к элементам массива осуществляется по индексу, который указывает на положение элемента в массиве. В случае одномерного массива индекс указывается в квадратных скобках []. Для многомерных массивов индексы указываются через запятую внутри квадратных скобок.

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

int[] numbers = { 10, 20, 30, 40, 50 };
int firstNumber = numbers[0]; // Доступ к первому элементу массива, результат: 10
int lastNumber = numbers[4]; // Доступ к последнему элементу массива, результат: 50

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

int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
int element = matrix[1, 2]; // Доступ к элементу на второй строке и третьем столбце, результат: 6
Циклы для работы с массивами

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

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

int[] numbers = { 1, 2, 3, 4, 5 };

for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"Элемент {i}: {numbers[i]}");
}

В этом примере цикл for проходит по каждому элементу массива numbers от начала до конца, используя длину массива (numbers.Length) для определения количества итераций.

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

string[] names = { "Alice", "Bob", "Charlie" };

foreach (string name in names)
{
Console.WriteLine(name);
}

Цикл foreach предназначен для итерации по элементам массива или коллекции без необходимости управления индексами. В этом примере foreach проходит по каждому элементу массива names и выводит его на экран.

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

int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};

for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
Console.Write(matrix[i, j] + " ");
}
Console.WriteLine();
}

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

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

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

Array.Sort

Метод Array.Sort сортирует элементы массива в порядке возрастания (по умолчанию). Этот метод может работать с одномерными массивами любых типов, для которых определен порядок сортировки.

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

int[] numbers = { 5, 3, 8, 1, 4 };
Array.Sort(numbers); // Сортировка массива

foreach (int number in numbers)
{
Console.WriteLine(number); // Вывод: 1, 3, 4, 5, 8
}
Array.Reverse

Метод Array.Reverse изменяет порядок элементов в массиве на противоположный, то есть последний элемент становится первым, а первый — последним.

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

int[] numbers = { 1, 2, 3, 4, 5 };
Array.Reverse(numbers); // Обратный порядок элементов массива

foreach (int number in numbers)
{
Console.WriteLine(number); // Вывод: 5, 4, 3, 2, 1
}
Array.IndexOf

Метод Array.IndexOf возвращает индекс первого вхождения указанного элемента в массиве. Если элемент не найден, метод возвращает -1.

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

string[] names = { "Alice", "Bob", "Charlie" };
int index = Array.IndexOf(names, "Bob"); // Результат: 1

Console.WriteLine(index); // Вывод: 1
Array.Resize

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

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

int[] numbers = { 1, 2, 3 };
Array.Resize(ref numbers, 5); // Изменение размера массива до 5 элементов

numbers[3] = 4;
numbers[4] = 5;

foreach (int number in numbers)
{
Console.WriteLine(number); // Вывод: 1, 2, 3, 4, 5
}

В этом примере размер массива numbers увеличивается до 5 элементов. Первоначальные элементы сохраняются, а новые элементы инициализируются значениями по умолчанию (в случае типа int — 0). Затем эти элементы можно инициализировать явно, как показано выше.

Array.Copy

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

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

int[] sourceArray = { 1, 2, 3, 4, 5 };
int[] destinationArray = new int[5];

Array.Copy(sourceArray, destinationArray, sourceArray.Length); // Копирование элементов

foreach (int number in destinationArray)
{
Console.WriteLine(number); // Вывод: 1, 2, 3, 4, 5
}

В этом примере метод Array.Copy копирует все элементы из массива sourceArray в массив destinationArray.

Array.Clear

Метод Array.Clear очищает часть массива, заменяя элементы значениями по умолчанию для их типа. Этот метод полезен, если нужно быстро "обнулить" часть массива.

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

int[] numbers = { 1, 2, 3, 4, 5 };
Array.Clear(numbers, 1, 3); // Обнуление трех элементов, начиная с индекса 1

foreach (int number in numbers)
{
Console.WriteLine(number); // Вывод: 1, 0, 0, 0, 5
}

В этом примере метод Array.Clear заменяет элементы массива numbers на нулевые значения (в случае типа int) начиная с индекса 1 и в течение трех элементов.

Выводы по разделу

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

2.2. Списки (List)

Разница между массивами и списками

Массивы и списки (List<T>) в C# представляют собой коллекции данных, однако они различаются по ряду ключевых характеристик, что определяет их использование в различных сценариях разработки.

  1. Размер:

    • Массивы: Массивы имеют фиксированный размер, который необходимо задать при их инициализации. После создания массива его размер нельзя изменить, что может быть ограничением, если заранее неизвестно, сколько элементов потребуется хранить.
    • Списки: Списки, представленные типом List<T>, являются динамическими коллекциями, размер которых может изменяться во время выполнения программы. Список автоматически расширяется при добавлении новых элементов, что делает его более гибким в использовании.
  2. Производительность:

    • Массивы: Массивы обеспечивают высокую производительность при доступе к элементам, поскольку элементы хранятся в непрерывных областях памяти. Доступ к элементам осуществляется с использованием индексов и происходит за постоянное время (O(1)).
    • Списки: Списки также обеспечивают доступ к элементам за постоянное время (O(1)) благодаря внутренней реализации на основе массивов. Однако операции, связанные с изменением размера списка (например, добавление элементов), могут требовать пересоздания внутреннего массива и перемещения элементов, что влияет на производительность.
  3. Функциональность:

    • Массивы: Массивы предоставляют базовый набор возможностей для хранения и доступа к данным, но не предлагают встроенных методов для динамического изменения своей структуры (например, добавления или удаления элементов).
    • Списки: Списки предоставляют широкий набор методов для работы с элементами, включая добавление (Add), удаление (Remove), вставку (Insert), очистку (Clear) и многое другое. Эти методы упрощают работу с коллекцией данных, особенно в сценариях, где размер коллекции изменяется.
  4. Типизация:

    • Массивы: Массивы могут быть массивами значимых типов, ссылочных типов или универсальными (массивы объектов).
    • Списки: Списки в C# являются обобщенными (List<T>), что означает, что они могут работать с любым типом данных, указанным в параметре типа <T>. Это позволяет избежать боксовки (boxing) и анбоксовки (unboxing), что может быть актуально для списков значимых типов.

Объявление и инициализация списков

Списки (List<T>) являются частью пространства имен System.Collections.Generic и требуют его подключения с помощью директивы using.

Объявление списка

Объявление списка включает указание типа элементов, которые будут храниться в списке, заключенного в угловые скобки <T>, и создание экземпляра списка с использованием ключевого слова new.

Синтаксис:

List<тип> имяСписка = new List<тип>();
Инициализация списка

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

1. Инициализация пустого списка:

List<int> numbers = new List<int>();

Этот пример создает пустой список numbers, который будет хранить элементы типа int. В дальнейшем элементы могут быть добавлены в этот список с помощью метода Add.

2. Инициализация списка с элементами:

List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

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

Основные методы работы со списками (Add, Remove, Insert, Clear)

Add

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

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

List<int> numbers = new List<int>();
numbers.Add(10); // Добавление элемента 10 в список
numbers.Add(20); // Добавление элемента 20 в список

В этом примере в список numbers добавляются два элемента: 10 и 20. Эти элементы будут размещены в списке в порядке добавления.

Remove

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

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

List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
names.Remove("Bob"); // Удаление элемента "Bob" из списка

В этом примере из списка names удаляется элемент "Bob". После выполнения этой операции список будет содержать два элемента: "Alice" и "Charlie".

Insert

Метод Insert вставляет элемент в список на указанную позицию. Элементы, находящиеся после указанной позиции, смещаются вправо.

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

List<string> names = new List<string> { "Alice", "Charlie" };
names.Insert(1, "Bob"); // Вставка элемента "Bob" на позицию с индексом 1

В этом примере элемент "Bob" вставляется в список на позицию с индексом 1, между "Alice" и "Charlie". После вставки список будет содержать элементы "Alice", "Bob" и "Charlie".

Clear

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

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

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Clear(); // Очистка списка

После вызова метода Clear список numbers будет пустым, то есть не будет содержать ни одного элемента.

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

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

1. Работа со списком целых чисел:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// Добавление нового элемента
numbers.Add(6);

// Удаление элемента
numbers.Remove(3);

// Вставка элемента на определенную позицию
numbers.Insert(2, 99);

// Итерация по элементам списка и вывод на экран
foreach (int number in numbers)
{
Console.WriteLine(number);
}

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

2. Управление списком строк:

List<string> tasks = new List<string> { "Buy groceries", "Call John", "Finish report" };

// Добавление новой задачи
tasks.Add("Clean the house");

// Удаление задачи
tasks.Remove("Call John");

// Очистка списка после выполнения всех задач
tasks.Clear();

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

3. Использование списка объектов:

class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 }
};

// Добавление нового человека в список
people.Add(new Person { Name = "Charlie", Age = 35 });

// Удаление человека по имени
people.RemoveAll(p => p.Name == "Bob");

// Вывод информации о людях в списке
foreach (Person person in people)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}

В этом примере используется список объектов типа Person, который имеет свойства Name и Age. Сначала список инициализируется двумя объектами, затем добавляется новый объект и удаляется объект по имени с использованием метода RemoveAll. В конце выводится информация о каждом человеке в списке.

Выводы по разделу

Списки (List<T>) в C# являются мощным и гибким инструментом для работы с коллекциями данных. Они предоставляют богатый набор методов для управления элементами, что делает их предпочтительным выбором в ситуациях, когда требуется динамическое изменение размера коллекции. В отличие от массивов, списки позволяют легко добавлять, удалять и вставлять элементы, что делает их более удобными для большинства практических задач. Понимание основ работы со списками и их использование позволяет создавать более гибкие и адаптивные приложения.

2.3. Структуры (struct)

Понятие структур, объявление и использование

Структуры (struct) в языке программирования C# представляют собой пользовательские типы данных, которые позволяют объединять несколько связанных переменных (или членов) в единую логическую единицу. Структуры, подобно классам, могут содержать поля, свойства, методы и даже события. Однако структуры обладают уникальной особенностью: они являются значимыми типами (value types), а не ссылочными (reference types). Это означает, что они хранятся в стеке (stack), а не в куче (heap), и передаются по значению, а не по ссылке.

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

Объявление структуры

Объявление структуры начинается с ключевого слова struct, за которым следует имя структуры и тело структуры, заключенное в фигурные скобки {}. Внутри тела структуры могут быть объявлены поля, свойства, методы, конструкторы и другие члены, которые определяют поведение и состояние экземпляра структуры.

Синтаксис:

public struct ИмяСтруктуры
{
// Поля структуры
public тип Поле1;
public тип Поле2;

// Свойства
public тип Свойство1 { get; set; }

// Методы
public тип Метод1()
{
// Тело метода
}

// Конструкторы
public ИмяСтруктуры(тип параметр1, тип параметр2)
{
Поле1 = параметр1;
Поле2 = параметр2;
}
}
Использование структуры

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

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

public struct Point
{
public int X;
public int Y;

// Конструктор
public Point(int x, int y)
{
X = x;
Y = y;
}

// Метод для вычисления расстояния до начала координат
public double DistanceFromOrigin()
{
return Math.Sqrt(X * X + Y * Y);
}
}

// Использование структуры
Point point1 = new Point(3, 4); // Создание экземпляра структуры через конструктор
Console.WriteLine($"Point1: ({point1.X}, {point1.Y})"); // Вывод координат точки

Point point2; // Создание экземпляра без инициализации
point2.X = 5; // Прямое присвоение значений полям
point2.Y = 7;
Console.WriteLine($"Point2: ({point2.X}, {point2.Y})");

double distance = point1.DistanceFromOrigin(); // Вызов метода структуры
Console.WriteLine($"Расстояние от начала координат до Point1: {distance}");

В данном примере структура Point представляет собой точку на двумерной плоскости с координатами X и Y. В структуре также реализован метод DistanceFromOrigin, который вычисляет расстояние от точки до начала координат. Экземпляры структуры создаются как через конструктор, так и через прямое присвоение значений полям.

Различие между структурами и классами

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

  1. Тип данных:

    • Структуры: Структуры являются значимыми типами (value types). Это означает, что они хранятся в стеке и передаются по значению. Когда структура присваивается другой переменной или передается в метод, создается полная копия структуры.
    • Классы: Классы являются ссылочными типами (reference types). Они хранятся в куче, и переменные, которые указывают на объекты классов, содержат ссылки на эти объекты. При присваивании объекта класса другой переменной или передаче его в метод копируется ссылка, а не сам объект.
  2. Наследование:

    • Структуры: Структуры не поддерживают наследование от других структур или классов, что означает, что они не могут быть базовыми или производными типами. Однако структуры могут реализовывать интерфейсы.
    • Классы: Классы поддерживают наследование, что позволяет создавать иерархии классов. Производные классы могут наследовать и переопределять методы и свойства базовых классов.
  3. Производительность:

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

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

    • Структуры: Изменения в одной копии структуры не влияют на другие копии, так как каждая копия структуры независима от других.
    • Классы: Поскольку классы передаются по ссылке, изменения в одном экземпляре класса могут быть видны в других переменных, указывающих на этот экземпляр.

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

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

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

public struct ComplexNumber
{
public double Real { get; set; }
public double Imaginary { get; set; }

public ComplexNumber(double real, double imaginary)
{
Real = real;
Imaginary = imaginary;
}

public double Magnitude()
{
return Math.Sqrt(Real * Real + Imaginary * Imaginary);
}

public override string ToString()
{
return $"{Real} + {Imaginary}i";
}
}

// Пример использования
ComplexNumber number = new ComplexNumber(3, 4);
Console.WriteLine($"Комплексное число: {number}");
Console.WriteLine($"Модуль: {number.Magnitude()}");

В этом примере структура ComplexNumber используется для представления комплексного числа, состоящего из вещественной и мнимой частей. Структура включает метод Magnitude для вычисления модуля комплексного числа и метод ToString для вывода комплексного числа в виде строки.

2. Структура для хранения данных о цвете:

public struct Color
{
public byte Red { get; set; }
public byte Green { get; set; }
public byte Blue { get; set; }

public Color(byte red, byte green, byte blue)
{
Red = red;
Green = green;
Blue = blue;
}

public override string ToString()
{
return $"RGB({Red}, {Green}, {Blue})";
}
}

// Пример использования
Color color = new Color(255, 0, 0);
Console.WriteLine($"Цвет: {color}");

В этом примере структура Color используется для представления цвета в модели RGB. Поля Red, Green и Blue хранят компоненты цвета в виде байтов. Структура также содержит метод ToString, который выводит цвет в формате строки.

3. Структура для представления временного интервала:

public struct TimeInterval
{
public int Hours { get; set; }
public int Minutes { get; set; }

public TimeInterval(int hours, int minutes)
{
Hours = hours;
Minutes = minutes;
}

public override string ToString()
{
return $"{Hours}h {Minutes}m";
}
}

// Пример использования
TimeInterval interval = new TimeInterval(2, 30);
Console.WriteLine($"Интервал времени: {interval}");

В этом примере структура TimeInterval используется для представления временного интервала, состоящего из часов и минут. Поля Hours и Minutes хранят соответствующие компоненты времени. Конструктор структуры инициализирует эти поля при создании экземпляра. Метод ToString возвращает строковое представление временного интервала, например, 2h 30m.

Выводы по разделу

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

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

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

2.4. Классы (class)

Обзор ключевых понятий: объект, экземпляр, поля, методы

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

Объект и экземпляр

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

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

Пример:

public class Car
{
public string Make; // Марка автомобиля
public string Model; // Модель автомобиля
public int Year; // Год выпуска

public void StartEngine()
{
Console.WriteLine("Двигатель заведен.");
}
}

// Создание экземпляра класса Car
Car myCar = new Car();

В этом примере Car — это класс, а myCar — объект или экземпляр этого класса. Экземпляр myCar содержит данные, описывающие конкретный автомобиль, и методы, позволяющие взаимодействовать с ним.

Поля

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

Пример поля:

public class Person
{
public string Name; // Имя
public int Age; // Возраст
}

В данном примере класс Person содержит два поля: Name и Age, которые будут хранить данные о каждом экземпляре Person.

Методы

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

Пример метода:

public class Person
{
public string Name;
public int Age;

public void Introduce()
{
Console.WriteLine($"Меня зовут {Name}, мне {Age} лет.");
}
}

В этом примере класс Person содержит метод Introduce, который выводит информацию о человеке, основанную на данных, хранящихся в полях Name и Age.

Разница между ссылочными и значимыми типами

Типы данных в C# можно разделить на две большие категории: ссылочные типы (reference types) и значимые типы (value types). Эти типы данных отличаются тем, как они хранятся в памяти и как передаются между частями программы.

Ссылочные типы

Ссылочные типы (reference types) включают классы, интерфейсы, делегаты и массивы. Переменные ссылочных типов хранят не сами данные, а ссылки на области памяти, где находятся данные. Объекты ссылочных типов создаются в куче (heap), а ссылки на них хранятся в стеке (stack).

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

Пример:

public class Book
{
public string Title;
public string Author;
}

Book book1 = new Book { Title = "1984", Author = "George Orwell" };
Book book2 = book1;

book2.Title = "Animal Farm";

Console.WriteLine(book1.Title); // Вывод: "Animal Farm"

В этом примере обе переменные book1 и book2 ссылаются на один и тот же объект в памяти. Изменение заголовка через book2 также изменяет заголовок в book1, потому что это один и тот же объект.

Значимые типы

Значимые типы (value types) включают структуры (struct), перечисления (enum) и все примитивные типы данных, такие как int, char, bool и т.д. Переменные значимых типов хранят непосредственно свои значения, а не ссылки на объекты. Эти значения хранятся в стеке.

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

Пример:

int a = 5;
int b = a;

b = 10;

Console.WriteLine(a); // Вывод: 5
Console.WriteLine(b); // Вывод: 10

В этом примере переменные a и b хранят независимые копии значения. Изменение b не влияет на a, так как обе переменные содержат собственные копии данных.

Примеры объявления и использования классов

Классы в C# используются для создания объектов, которые инкапсулируют данные и методы для их обработки. Рассмотрим несколько примеров, демонстрирующих создание и использование классов.

Пример 1: Класс для моделирования автомобиля
public class Car
{
public string Make { get; set; } // Марка автомобиля
public string Model { get; set; } // Модель автомобиля
public int Year { get; set; } // Год выпуска

public void StartEngine()
{
Console.WriteLine($"{Make} {Model} заведен.");
}

public void StopEngine()
{
Console.WriteLine($"{Make} {Model} заглушен.");
}
}

// Использование класса Car
Car myCar = new Car
{
Make = "Toyota",
Model = "Corolla",
Year = 2020
};

myCar.StartEngine(); // Вывод: "Toyota Corolla заведен."
myCar.StopEngine(); // Вывод: "Toyota Corolla заглушен."

В этом примере класс Car содержит три свойства (Make, Model, Year) и два метода (StartEngine и StopEngine). Создается экземпляр класса Car, инициализируются его свойства, и вызываются методы для выполнения действий.

Пример 2: Класс для представления банковского счета
public class BankAccount
{
public string AccountNumber { get; private set; }
public decimal Balance { get; private set; }

public BankAccount(string accountNumber, decimal initialBalance)
{
AccountNumber = accountNumber;
Balance = initialBalance;
}

public void Deposit(decimal amount)
{
Balance += amount;
Console.WriteLine($"Депозит: {amount:C}. Баланс: {Balance:C}.");
}

public void Withdraw(decimal amount)
{
if (amount <= Balance)
{
Balance -= amount;
Console.WriteLine($"Снятие: {amount:C}. Баланс: {Balance:C}.");
}
else
{
Console.WriteLine("Недостаточно средств.");
}
}
}

// Использование класса BankAccount
BankAccount account = new BankAccount("123456789", 1000);
account.Deposit(200); // Вывод: "Депозит: $200.00. Баланс: $1200.00."
account.Withdraw(150); // Вывод: "Снятие: $150.00. Баланс: $1050.00."

В этом примере класс BankAccount моделирует банковский счет с двумя свойствами: AccountNumber и Balance. Методы Deposit и Withdraw позволяют выполнять операции пополнения и снятия средств. Конструктор класса инициализирует номер счета и начальный баланс. Также показано использование методов для выполнения операций с банковским счетом.

Пример 3: Класс для представления сотрудника
using System.Text;
public class Employee
{
public string Name { get; set; }
public string Position { get; set; }
public decimal Salary { get; set; }

public void Work()
{
Console.WriteLine($"{Name} работает как {Position}.");
}

public void Promote(string newPosition, decimal newSalary)
{
Position = newPosition;
Salary = newSalary;
Console.WriteLine($"{Name} был повышен до {Position} с зарплатой {Salary:C}.");
}
}

Console.OutputEncoding = UTF8Encoding.UTF8;
// Использование класса Employee
Employee employee = new Employee
{
Name = "John Doe",
Position = "Software Developer",
Salary = 80000
};

employee.Work(); // Вывод: "John Doe работает как Software Developer."
employee.Promote("Senior Software Developer", 95000); // Вывод: "John Doe был повышен до Senior Software Developer с зарплатой $95,000.00."

В этом примере класс Employee представляет собой модель сотрудника с тремя свойствами: Name, Position и Salary. Класс содержит методы Work, который выводит информацию о текущей работе сотрудника, и Promote, который изменяет должность и зарплату сотрудника. При создании экземпляра класса Employee задаются начальные значения свойств, после чего вызываются методы, демонстрируя возможные действия с объектом сотрудника.

Выводы по разделу

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

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

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

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

3. Работа со строками

3.1. Основные операции со строками

Объявление строк, инициализация, использование

Строка в C# — это последовательность символов, представляющая текстовые данные. Строки являются ссылочным типом, и в языке C# они представлены классом String, который предоставляет множество методов для работы с текстом. Строки в C# являются неизменяемыми (immutable), что означает, что после создания строки её значение нельзя изменить. Любая операция, которая кажется изменяющей строку, на самом деле создает новую строку.

Объявление строк

Для объявления строки в C# используется тип string, который является синонимом типа System.String.

Синтаксис:

string имяПеременной;

Пример:

string greeting;

В этом примере объявляется переменная greeting типа string, которая пока не инициализирована.

Инициализация строк

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

1. Инициализация с использованием строкового литерала:

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

string greeting = "Hello, World!";

В этом примере строка "Hello, World!" присваивается переменной greeting.

2. Инициализация с помощью конструктора класса String:

char[] letters = { 'H', 'e', 'l', 'l', 'o' };
string greeting = new string(letters);

В данном примере создается строка greeting из массива символов letters с использованием конструктора класса String.

3. Инициализация путем копирования другой строки:

string original = "Hello, World!";
string copy = original;

Здесь строка copy инициализируется значением строки original. Оба этих объекта будут ссылаться на один и тот же объект в памяти, так как строки неизменяемы.

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

После объявления и инициализации строки можно использовать в различных операциях, таких как объединение (конкатенация), сравнение, извлечение подстрок и т.д.

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

string name = "Alice";
Console.WriteLine("Hello, " + name + "!"); // Вывод: Hello, Alice!

В этом примере строка name используется для вывода приветственного сообщения в консоль.

Конкатенация строк (+ и String.Concat)

Конкатенация строк — это операция объединения двух или более строк в одну. В C# конкатенация может быть выполнена с использованием оператора + или метода String.Concat.

Конкатенация с использованием оператора +

Оператор + является наиболее простым способом объединения строк. Он создает новую строку, состоящую из значений всех объединяемых строк.

Пример:

string firstName = "John";
string lastName = "Doe";
string fullName = firstName + " " + lastName;
Console.WriteLine(fullName); // Вывод: John Doe

В этом примере строки firstName и lastName объединяются с помощью оператора +, создавая строку fullName, которая затем выводится на экран.

Конкатенация с использованием метода String.Concat

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

Пример:

string part1 = "C#";
string part2 = " is";
string part3 = " awesome!";
string message = String.Concat(part1, part2, part3);
Console.WriteLine(message); // Вывод: C# is awesome!

В этом примере метод String.Concat объединяет три строки в одну, которая затем выводится на экран.

Интерполяция строк ($)

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

Пример:

string name = "Alice";
int age = 30;
string greeting = $"Hello, {name}! You are {age} years old.";
Console.WriteLine(greeting); // Вывод: Hello, Alice! You are 30 years old.

В этом примере интерполяция строк используется для вставки значений переменных name и age непосредственно в строку greeting. Этот подход делает код более читаемым и менее подверженным ошибкам, чем использование конкатенации.

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

Пример с выражениями:

string name = "Bob";
int apples = 3;
int oranges = 5;
string message = $"{name} has {apples + oranges} fruits.";
Console.WriteLine(message); // Вывод: Bob has 8 fruits.

В этом примере выражение внутри фигурных скобок {apples + oranges} вычисляется и результат вставляется в строку.

Выводы по разделу

Строки в C# являются одним из наиболее часто используемых типов данных, предоставляя мощные и гибкие возможности для работы с текстом. Объявление, инициализация и использование строк — это основополагающие операции, которые каждый разработчик должен хорошо понимать. Конкатенация строк с использованием оператора + или метода String.Concat позволяет объединять строки, но может привести к снижению производительности при работе с большими объемами данных.

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

Эти методы и техники являются основой работы со строками и широко используются в разработке программ на C#.

3.2. Методы класса String

Класс String в C# предоставляет множество методов, которые позволяют эффективно манипулировать строками, выполняя такие операции, как поиск, извлечение подстрок, замена символов, изменение регистра и разбиение строки на части. Рассмотрим основные методы класса String и примеры их использования.

Substring

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

Синтаксис:
string Substring(int startIndex);
string Substring(int startIndex, int length);
  • startIndex: начальная позиция подстроки.
  • length: количество символов в подстроке (опционально).
Примеры использования:

1. Извлечение подстроки до конца строки:

string text = "Hello, World!";
string substring = text.Substring(7);
Console.WriteLine(substring); // Вывод: "World!"

В этом примере извлекается подстрока, начиная с индекса 7, и до конца строки. Подстрока содержит текст "World!".

2. Извлечение подстроки заданной длины:

string text = "Hello, World!";
string substring = text.Substring(7, 5);
Console.WriteLine(substring); // Вывод: "World"

Здесь извлекается подстрока длиной 5 символов, начиная с индекса 7, что приводит к получению подстроки "World".

IndexOf

Метод IndexOf возвращает индекс первого вхождения указанного символа или строки в данной строке. Если символ или строка не найдены, метод возвращает -1.

Синтаксис:
int IndexOf(char value);
int IndexOf(string value);
int IndexOf(char value, int startIndex);
int IndexOf(string value, int startIndex);
  • value: символ или строка для поиска.
  • startIndex: начальная позиция для начала поиска (опционально).
Примеры использования:

1. Поиск первого вхождения символа:

string text = "Hello, World!";
int index = text.IndexOf('o');
Console.WriteLine(index); // Вывод: 4

В этом примере метод IndexOf возвращает индекс первого вхождения символа 'o', который находится на позиции 4.

2. Поиск строки в строке:

string text = "Hello, World!";
int index = text.IndexOf("World");
Console.WriteLine(index); // Вывод: 7

Здесь метод возвращает индекс, с которого начинается подстрока "World".

Replace

Метод Replace заменяет все вхождения указанного символа или подстроки на другой символ или строку.

Синтаксис:
string Replace(char oldChar, char newChar);
string Replace(string oldValue, string newValue);
  • oldChar/oldValue: символ или строка для замены.
  • newChar/newValue: символ или строка, на которую нужно заменить.
Примеры использования:

1. Замена символа:

string text = "Hello, World!";
string newText = text.Replace('o', '0');
Console.WriteLine(newText); // Вывод: "Hell0, W0rld!"

В этом примере все вхождения символа 'o' заменяются на '0', что приводит к новой строке "Hell0, W0rld!".

2. Замена подстроки:

string text = "Hello, World!";
string newText = text.Replace("World", "C#");
Console.WriteLine(newText); // Вывод: "Hello, C#!"

Здесь подстрока "World" заменяется на "C#", в результате чего получаем строку "Hello, C#!".

ToUpper

Метод ToUpper возвращает копию строки, в которой все буквенные символы преобразованы в верхний регистр.

Синтаксис:
string ToUpper();
Пример использования:
string text = "Hello, World!";
string upperText = text.ToUpper();
Console.WriteLine(upperText); // Вывод: "HELLO, WORLD!"

В этом примере метод ToUpper преобразует все буквенные символы в строке "Hello, World!" в верхний регистр, результатом чего является строка "HELLO, WORLD!".

ToLower

Метод ToLower возвращает копию строки, в которой все буквенные символы преобразованы в нижний регистр.

Синтаксис:
string ToLower();
Пример использования:
string text = "Hello, World!";
string lowerText = text.ToLower();
Console.WriteLine(lowerText); // Вывод: "hello, world!"

В этом примере метод ToLower преобразует все буквенные символы в строке "Hello, World!" в нижний регистр, результатом чего является строка "hello, world!".

Split

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

Синтаксис:
string[] Split(char separator);
string[] Split(string separator);
  • separator: символ или строка, используемые в качестве разделителя.
Примеры использования:

1. Разбиение строки по пробелам:

string text = "Hello World from C#";
string[] words = text.Split(' ');
foreach (string word in words)
{
Console.WriteLine(word);
}
// Вывод:
// Hello
// World
// from
// C#

В этом примере метод Split разделяет строку "Hello World from C#" на массив подстрок, используя пробел в качестве разделителя.

2. Разбиение строки по запятой:

string csv = "apple,banana,orange";
string[] fruits = csv.Split(',');
foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}
// Вывод:
// apple
// banana
// orange

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

Выводы по разделу

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

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

3.3. Работа с пустыми и null-строками

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

Проверка строк на пустоту и null

В C# существуют два метода, которые позволяют проверять строки на пустоту и значение null: string.IsNullOrEmpty и string.IsNullOrWhiteSpace. Эти методы помогают избежать ошибок и упрощают логику проверки строк.

string.IsNullOrEmpty

Метод string.IsNullOrEmpty возвращает true, если строка является пустой ("") или равна null. В противном случае метод возвращает false.

Синтаксис:
bool IsNullOrEmpty(string value);
  • value: строка, которую необходимо проверить.
Пример использования:
string text1 = null;
string text2 = "";
string text3 = "Hello, World!";

bool isText1EmptyOrNull = string.IsNullOrEmpty(text1); // true
bool isText2EmptyOrNull = string.IsNullOrEmpty(text2); // true
bool isText3EmptyOrNull = string.IsNullOrEmpty(text3); // false

Console.WriteLine(isText1EmptyOrNull); // Вывод: true
Console.WriteLine(isText2EmptyOrNull); // Вывод: true
Console.WriteLine(isText3EmptyOrNull); // Вывод: false

В этом примере метод string.IsNullOrEmpty используется для проверки трех строк. Первая строка (text1) равна null, вторая (text2) является пустой строкой, а третья (text3) содержит текст. Метод правильно определяет, какие из строк являются пустыми или null.

string.IsNullOrWhiteSpace

Метод string.IsNullOrWhiteSpace расширяет функциональность метода string.IsNullOrEmpty и возвращает true, если строка является пустой, равна null или состоит только из пробельных символов (например, пробелы, табуляции и т.п.).

Синтаксис:
bool IsNullOrWhiteSpace(string value);
  • value: строка, которую необходимо проверить.
Пример использования:
string text1 = null;
string text2 = " ";
string text3 = "\t\n";
string text4 = "Hello, World!";

bool isText1EmptyOrWhitespace = string.IsNullOrWhiteSpace(text1); // true
bool isText2EmptyOrWhitespace = string.IsNullOrWhiteSpace(text2); // true
bool isText3EmptyOrWhitespace = string.IsNullOrWhiteSpace(text3); // true
bool isText4EmptyOrWhitespace = string.IsNullOrWhiteSpace(text4); // false

Console.WriteLine(isText1EmptyOrWhitespace); // Вывод: true
Console.WriteLine(isText2EmptyOrWhitespace); // Вывод: true
Console.WriteLine(isText3EmptyOrWhitespace); // Вывод: true
Console.WriteLine(isText4EmptyOrWhitespace); // Вывод: false

В этом примере метод string.IsNullOrWhiteSpace проверяет несколько строк, включая строку, состоящую из пробелов, и строку, состоящую из символов табуляции и новой строки. Метод возвращает true для всех строк, которые пусты, равны null или содержат только пробельные символы.

Безопасная работа со строками, чтобы избежать NullReferenceException

NullReferenceException — это исключение, которое возникает при попытке вызвать метод или получить доступ к члену объекта через ссылку, которая равна null. Такое исключение может произойти, если вы пытаетесь работать со строкой, которая не была инициализирована или была явно присвоена значение null.

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

  1. Проверка на null перед доступом к строке:

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

Пример:

string text = null;

if (text != null)
{
Console.WriteLine(text.Length); // Безопасно, так как проверка на null выполнена
}
else
{
Console.WriteLine("Строка равна null.");
}

В этом примере перед попыткой получения длины строки выполняется проверка на null, что предотвращает возникновение NullReferenceException.

  1. Использование методов string.IsNullOrEmpty и string.IsNullOrWhiteSpace:

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

Пример:

string text = null;

if (!string.IsNullOrEmpty(text))
{
Console.WriteLine(text.Length); // Выполнится, только если строка не пустая и не null
}
else
{
Console.WriteLine("Строка либо пустая, либо null.");
}

В этом примере используется метод string.IsNullOrEmpty, чтобы убедиться, что строка не является пустой или null, перед тем как получить её длину.

  1. Использование оператора объединения с null (??):

Оператор ?? позволяет указать значение по умолчанию, которое будет использоваться, если строка равна null.

Пример:

string text = null;
string result = text ?? "Значение по умолчанию";
Console.WriteLine(result); // Вывод: "Значение по умолчанию"

В этом примере, если строка text равна null, то используется значение по умолчанию "Значение по умолчанию". Это позволяет избежать работы с null и предотвращает NullReferenceException.

  1. Использование оператора условного доступа (?.):

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

Пример:

string text = null;
int? length = text?.Length; // Безопасно, вернет null, если text равен null
Console.WriteLine(length.HasValue ? length.ToString() : "Длина не определена."); // Вывод: "Длина не определена."

В этом примере используется оператор ?. для безопасного получения длины строки. Если строка text равна null, выражение text?.Length вернет null, а не вызовет исключение.

Выводы по разделу

Работа с пустыми и null-строками в C# требует осторожности, чтобы избежать распространенных ошибок, таких как NullReferenceException. Проверка строк с использованием методов string.IsNullOrEmpty и string.IsNullOrWhiteSpace позволяет надежно определить, содержит ли строка текст, или она пуста или равна null.

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

3.4. Форматирование строк

Форматирование строк в C# — это процесс создания строк, которые включают в себя числа, даты, валюту и другие данные в определенном формате. В C# существует несколько способов форматирования строк, включая использование метода String.Format и интерполяцию строк. Эти методы позволяют вставлять значения в строки и форматировать их в соответствии с определенными правилами, что делает вывод информации более структурированным и понятным.

Форматирование чисел и дат в строках (String.Format, интерполяция)

String.Format

Метод String.Format позволяет форматировать строки, вставляя в них значения и применяя к ним определенные форматы. Этот метод использует плейсхолдеры {index:format}, где index — это номер аргумента, передаваемого в метод, а format — строка формата, определяющая, как должно быть представлено значение.

Синтаксис:
string formattedString = String.Format("шаблон строки", аргументы...);
Примеры использования:

1. Форматирование чисел:

int number = 12345;
string formattedNumber = String.Format("Число: {0:N0}", number);
Console.WriteLine(formattedNumber); // Вывод: "Число: 12,345"

В этом примере число форматируется с использованием формата "N0", который добавляет запятые как разделители тысяч и не выводит десятичных знаков.

2. Форматирование даты:

DateTime today = DateTime.Now;
string formattedDate = String.Format("Сегодня: {0:dddd, MMMM dd, yyyy}", today);
Console.WriteLine(formattedDate); // Вывод: "Сегодня: понедельник, сентябрь 04, 2023"

Здесь используется формат даты "dddd, MMMM dd, yyyy", который выводит день недели, название месяца, день и год.

3. Форматирование валюты:

decimal price = 99.99m;
string formattedPrice = String.Format("Цена: {0:C}", price);
Console.WriteLine(formattedPrice); // Вывод: "Цена: $99.99"

Формат "C" используется для форматирования значения как валюты, с учетом региональных настроек.

Интерполяция строк

Интерполяция строк предоставляет более удобный и современный способ форматирования строк в C#. Она позволяет напрямую вставлять выражения и переменные в строку, используя синтаксис $"{expression}". Внутри фигурных скобок {} можно указать формат для чисел, дат и других типов данных.

Синтаксис:
string formattedString = $"шаблон строки {переменная:формат}";
Примеры использования:

1. Форматирование чисел с интерполяцией:

int number = 12345;
string formattedNumber = $"Число: {number:N0}";
Console.WriteLine(formattedNumber); // Вывод: "Число: 12,345"

Этот пример аналогичен предыдущему примеру с String.Format, но использует интерполяцию строк для более удобного и читаемого форматирования.

2. Форматирование даты с интерполяцией:

DateTime today = DateTime.Now;
string formattedDate = $"Сегодня: {today:dddd, MMMM dd, yyyy}";
Console.WriteLine(formattedDate); // Вывод: "Сегодня: понедельник, сентябрь 04, 2023"

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

3. Форматирование валюты с интерполяцией:

decimal price = 99.99m;
string formattedPrice = $"Цена: {price:C}";
Console.WriteLine(formattedPrice); // Вывод: "Цена: $99.99"

Этот пример показывает, как можно форматировать значения валюты с использованием интерполяции строк.

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

Форматирование чисел

Числа можно форматировать с использованием различных спецификаторов формата, таких как:

  • "N": формат чисел с запятыми в качестве разделителей тысяч и фиксированным количеством десятичных знаков.
  • "D": форматирование целых чисел с фиксированным количеством цифр (заполняет слева нулями).
  • "X": форматирование числа в шестнадцатеричном представлении.

Пример:

int number = 255;

string numberAsDecimal = String.Format("Десятичное: {0:D5}", number); // Вывод: "Десятичное: 00255"
string numberAsHex = String.Format("Шестнадцатеричное: {0:X}", number); // Вывод: "Шестнадцатеричное: FF"
string numberAsNumeric = String.Format("Число: {0:N}", number); // Вывод: "Число: 255.00"

Console.WriteLine(numberAsDecimal);
Console.WriteLine(numberAsHex);
Console.WriteLine(numberAsNumeric);

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

Форматирование дат

Даты можно форматировать с использованием различных спецификаторов формата, таких как:

  • "d": краткий формат даты.
  • "D": длинный формат даты.
  • "t": краткий формат времени.
  • "T": длинный формат времени.
  • "f": полное представление даты и времени.

Пример:

DateTime now = DateTime.Now;

string shortDate = String.Format("Краткая дата: {0:d}", now); // Вывод: "Краткая дата: 9/4/2023"
string longDate = String.Format("Длинная дата: {0:D}", now); // Вывод: "Длинная дата: понедельник, 4 сентября 2023 г."
string shortTime = String.Format("Краткое время: {0:t}", now); // Вывод: "Краткое время: 10:15"
string longTime = String.Format("Длинное время: {0:T}", now); // Вывод: "Длинное время: 10:15:30"
string fullDateTime = String.Format("Полная дата и время: {0:f}", now); // Вывод: "Полная дата и время: понедельник, 4 сентября 2023 г. 10:15"

Console.WriteLine(shortDate);
Console.WriteLine(longDate);
Console.WriteLine(shortTime);
Console.WriteLine(longTime);
Console.WriteLine(fullDateTime);

В этом примере показаны различные способы форматирования даты и времени.

Форматирование валюты и процентов

Форматирование валюты и процентов также может быть выполнено с использованием соответствующих спецификаторов:

  • "C": форматирование в виде валюты.
  • "P": форматирование в виде процентов.

Пример:

decimal amount = 1234.56m;
double rate = 0.075;

string currency = String.Format("Валюта: {0:C}", amount); // Вывод: "Валюта: $1,234.56"
string percentage = String.Format("Процент: {0:P}", rate); // Вывод: "Процент: 7.50%"

Console.WriteLine(currency);
Console.WriteLine(percentage);

Здесь мы форматируем сумму как валюту и процентную ставку как процент.

Выводы по разделу

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

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

3.5. Сравнение строк

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

Методы сравнения: Equals, Compare, CompareTo

Equals

Метод Equals проверяет, равны ли две строки. Он возвращает true, если строки идентичны, и false, если они различаются.

Синтаксис:
bool Equals(string value);
bool Equals(string value, StringComparison comparisonType);
  • value: строка для сравнения.
  • comparisonType: указывает, как строки должны сравниваться (например, с учетом регистра или без учета).
Примеры использования:

1. Сравнение строк с учетом регистра:

string str1 = "Hello";
string str2 = "hello";

bool areEqual = str1.Equals(str2);
Console.WriteLine(areEqual); // Вывод: false

В этом примере строки str1 и str2 не равны, так как метод Equals по умолчанию учитывает регистр символов.

2. Сравнение строк без учета регистра:

string str1 = "Hello";
string str2 = "hello";

bool areEqual = str1.Equals(str2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(areEqual); // Вывод: true

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

Compare

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

  • 0 — строки равны.
  • < 0 — первая строка меньше второй.
  • > 0 — первая строка больше второй.
Синтаксис:
int Compare(string strA, string strB);
int Compare(string strA, string strB, bool ignoreCase);
int Compare(string strA, string strB, StringComparison comparisonType);
  • strA и strB: строки для сравнения.
  • ignoreCase: указывает, нужно ли игнорировать регистр при сравнении.
  • comparisonType: указывает, как строки должны сравниваться.
Примеры использования:

1. Простое сравнение:

string str1 = "apple";
string str2 = "banana";

int result = String.Compare(str1, str2);
Console.WriteLine(result); // Вывод: -1 (str1 < str2)

В этом примере строка "apple" меньше строки "banana", поэтому метод возвращает -1.

2. Сравнение без учета регистра:

string str1 = "Apple";
string str2 = "apple";

int result = String.Compare(str1, str2, true);
Console.WriteLine(result); // Вывод: 0 (строки равны без учета регистра)

Здесь строки сравниваются без учета регистра, и метод возвращает 0, указывая на то, что строки равны.

CompareTo

Метод CompareTo — это метод экземпляра строки, который выполняет сравнение с другой строкой и возвращает те же значения, что и Compare: 0, < 0 или > 0.

Синтаксис:
int CompareTo(string other);
  • other: строка для сравнения с текущим объектом строки.
Пример использования:
string str1 = "orange";
string str2 = "apple";

int result = str1.CompareTo(str2);
Console.WriteLine(result); // Вывод: 1 (str1 > str2)

В этом примере метод CompareTo возвращает 1, так как строка "orange" больше строки "apple".

Учет регистра при сравнении (StringComparison)

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

Основные значения StringComparison:
  • StringComparison.Ordinal: сравнение с учетом регистра, основанное на значениях кодовых точек Unicode.
  • StringComparison.OrdinalIgnoreCase: сравнение без учета регистра, основанное на значениях кодовых точек Unicode.
  • StringComparison.CurrentCulture: культурально-зависимое сравнение с учетом регистра, основанное на текущей культуре.
  • StringComparison.CurrentCultureIgnoreCase: культурально-зависимое сравнение без учета регистра, основанное на текущей культуре.
  • StringComparison.InvariantCulture: культурально-независимое сравнение с учетом регистра, основанное на инвариантной культуре.
  • StringComparison.InvariantCultureIgnoreCase: культурально-независимое сравнение без учета регистра, основанное на инвариантной культуре.
Примеры использования:

1. Сравнение с учетом регистра:

string str1 = "Hello";
string str2 = "hello";

bool areEqual = str1.Equals(str2, StringComparison.Ordinal);
Console.WriteLine(areEqual); // Вывод: false

Здесь строки сравниваются с учетом регистра, и метод Equals возвращает false, так как строки различаются по регистру.

2. Сравнение без учета регистра:

string str1 = "Hello";
string str2 = "hello";

bool areEqual = str1.Equals(str2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(areEqual); // Вывод: true

В этом примере строки сравниваются без учета регистра, и метод возвращает true, так как строки считаются равными.

Культурально-зависимое сравнение

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

Пример культурально-зависимого сравнения:
string str1 = "straße";  // Немецкое слово "улица"
string str2 = "strasse"; // Слово с похожим произношением

bool areEqual = str1.Equals(str2, StringComparison.CurrentCulture);
Console.WriteLine(areEqual); // Вывод зависит от текущей культуры

В этом примере результат сравнения строк "straße" и "strasse" может варьироваться в зависимости от текущей культуры системы. Например, в немецкой культуре они могут рассматриваться как равные.

Пример использования Compare с учетом культуры:
using System.Globalization;

string str1 = "café";
string str2 = "cafe";

int result = String.Compare(str1, str2, true, new CultureInfo("fr-FR"));
Console.WriteLine(result); // Вывод: 1 (str1 > str2)

В этом примере строки сравниваются с учетом французской культуры ("fr-FR"), где буква "é" может рассматриваться как отличающаяся от "e". Метод возвращает 1, указывая, что строка "café" больше строки "cafe".

Выводы по разделу

Сравнение строк в C# является гибким и мощным инструментом, который может учитывать регистр символов, культурные различия и другие важные факторы. Методы Equals, Compare и CompareTo предоставляют разработчикам различные способы сравнения строк в зависимости от их требований. Использование StringComparison позволяет точно контролировать, как строки сравниваются — с учетом регистра, культуры или без них.

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

Дополнительные материалы

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

Регулярные выражения (regular expressions) — это мощный инструмент для поиска, сопоставления и обработки текста на основе шаблонов. В C# регулярные выражения реализованы с помощью класса Regex, который находится в пространстве имен System.Text.RegularExpressions. Регулярные выражения позволяют выполнять сложные проверки и модификации строк, такие как проверка формата email, поиск шаблонов в тексте и замена текстовых фрагментов.

Основные элементы регулярных выражений:
  • Литералы: Простые текстовые символы, которые должны точно соответствовать тексту. Например, регулярное выражение cat найдет все вхождения слова "cat" в тексте.
  • Метасимволы: Специальные символы, такие как . (любой символ), \d (любая цифра), \w (любая буква или цифра), \s (любой пробельный символ).
  • Квантификаторы: Указывают количество повторений. Например, a* соответствует нулю или более вхождениям символа a, а a+ — одному или более вхождениям.

Примеры использования регулярных выражений:

1. Проверка формата email:

using System.Text.RegularExpressions;

string email = "example@example.com";
string pattern = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";

bool isValidEmail = Regex.IsMatch(email, pattern);
Console.WriteLine(isValidEmail); // Вывод: true

В этом примере используется регулярное выражение для проверки, соответствует ли строка формату email. Регулярное выражение ^[^@\s]+@[^@\s]+\.[^@\s]+$ проверяет, что строка начинается с одного или более символов, не являющихся пробелами или знаком @, содержит @, а затем доменное имя с точкой.

2. Поиск всех цифр в строке:

using System.Text.RegularExpressions;

string text = "There are 123 apples and 456 oranges.";
string pattern = @"\d+";

MatchCollection matches = Regex.Matches(text, pattern);

foreach (Match match in matches)
{
Console.WriteLine(match.Value); // Вывод: 123, 456
}

Здесь регулярное выражение \d+ используется для поиска всех последовательностей цифр в строке.

3. Замена текста с использованием регулярного выражения:

using System.Text.RegularExpressions;

string text = "The quick brown fox jumps over the lazy dog.";
string pattern = @"\bfox\b";
string replacement = "cat";

string result = Regex.Replace(text, pattern, replacement);
Console.WriteLine(result); // Вывод: "The quick brown cat jumps over the lazy dog."

В этом примере регулярное выражение \bfox\b находит слово "fox" в тексте, и оно заменяется на "cat".

Примеры работы с более сложными типами данных (например, словари Dictionary)

Словари (Dictionary<TKey, TValue>) в C# — это коллекции, которые хранят пары "ключ-значение". Словарь позволяет быстро находить значение по ключу и является удобным инструментом для работы с данными, которые нужно организовать в виде сопоставления.

Основные операции со словарем:

  • Добавление элемента: С помощью метода Add.
  • Доступ к элементу: С использованием индексации по ключу.
  • Удаление элемента: С помощью метода Remove.
  • Проверка наличия ключа: С помощью метода ContainsKey.
Примеры использования:

1. Создание и инициализация словаря:

Dictionary<int, string> students = new Dictionary<int, string>
{
{ 1, "Alice" },
{ 2, "Bob" },
{ 3, "Charlie" }
};

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

2. Доступ к элементу по ключу:

string studentName = students[1];
Console.WriteLine(studentName); // Вывод: "Alice"

Здесь по ключу 1 извлекается имя студента "Alice".

3. Добавление и удаление элементов:

students.Add(4, "David");
students.Remove(2);

foreach (var student in students)
{
Console.WriteLine($"ID: {student.Key}, Name: {student.Value}");
}
// Вывод:
// ID: 1, Name: Alice
// ID: 3, Name: Charlie
// ID: 4, Name: David

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

4. Проверка наличия ключа:

if (students.ContainsKey(2))
{
Console.WriteLine("Студент с ID 2 найден.");
}
else
{
Console.WriteLine("Студент с ID 2 не найден.");
}
// Вывод: "Студент с ID 2 не найден."

Здесь проверяется наличие студента с ключом 2, и поскольку он был удален, сообщение будет, что студент не найден.

Рассмотрение примеров ошибок при преобразовании типов и их устранение

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

Пример 1: Ошибка при преобразовании строки в число (FormatException)

Проблема:

string input = "abc";
int number = int.Parse(input); // Это вызовет FormatException

В этом примере строка "abc" не может быть преобразована в целое число, и попытка вызовет исключение FormatException.

Решение:

string input = "abc";
if (int.TryParse(input, out int number))
{
Console.WriteLine($"Успешное преобразование: {number}");
}
else
{
Console.WriteLine("Ошибка: введенное значение не является числом.");
}
// Вывод: "Ошибка: введенное значение не является числом."

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

Пример 2: Ошибка при преобразовании типа (InvalidCastException)

Проблема:

object obj = "123";
int number = (int)obj; // Это вызовет InvalidCastException

Здесь возникает исключение InvalidCastException, так как строку "123" нельзя напрямую привести к типу int.

Решение:

object obj = "123";

if (obj is int)
{
int number = (int)obj;
Console.WriteLine($"Число: {number}");
}
else if (obj is string str && int.TryParse(str, out int parsedNumber))
{
Console.WriteLine($"Успешное преобразование строки в число: {parsedNumber}");
}
else
{
Console.WriteLine("Не удалось преобразовать объект в число.");
}
// Вывод: "Успешное преобразование строки в число: 123"

Этот пример показывает, как можно использовать оператор is для проверки типа объекта перед его преобразованием. Также используется метод TryParse для попытки преобразовать строку в число, что позволяет избежать ошибок.

Пример 3: Ошибка при преобразовании вещественного числа в целое (OverflowException)

Проблема:

double largeNumber = 1e20;
int number = (int)largeNumber; // Это может вызвать потерю данных и исключение OverflowException

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

Решение:

double largeNumber = 1e20;

try
{
int number = checked((int)largeNumber);
Console.WriteLine($"Число: {number}");
}
catch (OverflowException)
{
Console.WriteLine("Ошибка: переполнение при преобразовании числа.");
}
// Вывод: "Ошибка: переполнение при преобразовании числа."

Использование ключевого слова checked позволяет обнаружить переполнение и корректно обработать его с помощью блока try-catch.

Выводы по разделу

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

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