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

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

Практический алгоритм выбора преобразования

  1. Если источник ненадёжный (ввод пользователя, файл, API) - используйте TryParse.
  2. Если преобразование гарантировано по контракту - допустим явный cast.
  3. Если работаете с бизнес-логикой - избегайте dynamic без острой необходимости.
  4. Если тип может быть разным - сначала is, затем безопасная ветка обработки.

Ошибки, которые дорого стоят

  • Parse вместо TryParse в пользовательском вводе.
  • Массовое использование Convert в горячем коде без профилирования.
  • Приведения "на удачу" без проверки типа.

Короткий безопасный шаблон

if (!int.TryParse(input, out var amount))
{
return "Некорректное число";
}

if (amount < 0)
{
return "Число не может быть отрицательным";
}

Смежные статьи

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

Разработчику Архитектору

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

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

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

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

C# — статически и строго типизированный язык, как и Java. Это означает, что все типы проверяются на этапе компиляции, нельзя присвоить string в int без явного указания и нельзя вызвать метод, которого нет у типа - компилятор не пропустит.

Но всё же типы преобразовать можно.


Неявное приведение

Неявное приведение (implicit), происходит автоматически, когда нет риска потери данных.

int a = 100;
long b = a; // OK: int → long (без потерь)

Разбор:

  • int a = 100; создаёт переменную целочисленного типа int (32 бита) и записывает в неё литерал 100.
  • long b = a; демонстрирует неявное расширяющее преобразование: int автоматически приводится к long (64 бита).
  • Ключевой момент: при таком преобразовании диапазон целевого типа больше, поэтому риск потери данных отсутствует.
  • Проверка корректности выполняется на этапе компиляции, дополнительный runtime-cast не нужен.

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


Явное приведение

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

double d = 123.456;
int i = (int)d; // Явное приведение: 123 (дробная часть теряется)

Разбор:

  • double d = 123.456; хранит число с плавающей точкой двойной точности.
  • (int)d — явное приведение (cast), которое отбрасывает дробную часть, а не округляет значение.
  • После преобразования в i попадёт 123, потому что int хранит только целую часть.
  • Скобки с типом обязательны: без них компилятор блокирует потенциально опасное сужающее преобразование.

Без (int) получим ошибку компиляции - это используется для узкого приведения (наоборот long в int), приведения между несовместимыми типами, и приведения по иерархии наследования вниз (базовый в производный).


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

Таким образом, мы знакомимся с ещё несколькими операторами - операторами преобразования типов.


T

(T) — явное приведение. T - любой тип, допустим (int). Если тип не совпадает — InvalidCastException.

object obj = "Hello";
string s = (string)obj; // Работает
// string n = (string)123; // InvalidCastException

Разбор:

  • object obj = "Hello"; поднимает ссылку на строку до базового типа object.
  • string s = (string)obj; делает обратное приведение к string; оно успешно, потому что в obj реально лежит строка.
  • Закомментированная строка показывает опасность: если фактический тип несовместим, runtime выбрасывает InvalidCastException.
  • Итог: оператор (T) мощный, но небезопасный без предварительной проверки типа.

is

is — безопасная проверка типа.

if (obj is string s)
{
Console.WriteLine(s.Length);
}

Разбор:

  • is выполняет проверку фактического типа объекта без исключений.
  • Конструкция obj is string s одновременно проверяет тип и объявляет новую переменную s, если проверка прошла.
  • Внутри блока if переменная s уже гарантированно строка, поэтому безопасно вызывать s.Length.
  • Такой pattern matching предпочтительнее ручного as + null-проверки в простых сценариях.

Здесь выполняется проверка, можно ли привести объект к типу. Она не выбрасывает исключение, а с C# 7+ позволяет присвоить результат в переменную.


as

as — безопасное приведение (только для ссылочных типов), возвращает null, если приведение невозможно. Не работает со значимыми типами (но можно с nullable), его можно использовать когда ожидаем, что приведение может не сработать. Выглядит так:

string s = obj as string;
if (s != null)
{
Console.WriteLine(s.Length);
}

Разбор:

  • as пытается привести obj к string, но вместо исключения возвращает null, если приведение невозможно.
  • Именно поэтому обязательна проверка s != null перед обращением к членам строки.
  • Механика удобна, когда ожидается частая неудача приведения и не хочется работать через try/catch.
  • Важно: as применим к ссылочным типам и nullable-значимым типам.

Convert

Convert — универсальные методы преобразования, поддерживает множество типов и может преобразовывать между несовместимыми (например, bool в int). Но может выбрасывать исключения (FormatException, InvalidCastException) и выполняется медленнее, чем прямое приведение.

string s = "123";
int i = Convert.ToInt32(s);
double d = Convert.ToDouble("3.14");

Разбор:

  • Convert.ToInt32(s) разбирает строку и возвращает int, если формат корректен.
  • Convert.ToDouble("3.14") аналогично преобразует строку в double.
  • В отличие от прямого cast, Convert умеет ряд межтиповых преобразований через встроенную логику .NET.
  • При некорректном формате или null возможны исключения, поэтому для пользовательского ввода обычно выбирают TryParse.

Parse

Parse — выбрасывает исключение при ошибке:

int number = int.Parse("123"); // OK
// int fail = int.Parse("abc"); // FormatException

Разбор:

  • int.Parse("123") строго ожидает корректную числовую строку и возвращает результат типа int.
  • Если строка не соответствует формату числа ("abc"), метод выбрасывает FormatException.
  • Parse подходит, когда источник данных надёжный и ошибка действительно исключительная.
  • Для внешнего ввода безопаснее использовать TryParse, чтобы не строить контроль потока на исключениях.

TryParse

TryParse — безопасный способ (рекомендуется):

if (int.TryParse("abc", out int result))
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("Не удалось распарсить");
}

Разбор:

  • TryParse пытается преобразовать строку и возвращает bool: true при успехе, false при ошибке.
  • out int result объявляет переменную результата прямо в вызове и заполняется методом.
  • При неуспехе исключение не бросается, поэтому код предсказуем для ветвления и быстрее в массовых сценариях.
  • Такой шаблон считается базовым стандартом для валидации пользовательского ввода.

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

Чтобы проверять типы, можно использовать is, typeof(T) и obj.GetType().

  • is проверяет, является ли объект экземпляром типа;
  • typeof(T) возвращает Type для типа на этапе компиляции, допустим typeof(string);
  • obj.GetType() возвращает Type для объекта во время выполнения.

Пример:

object obj = "Hello";

Console.WriteLine(obj is string); // True
Console.WriteLine(obj.GetType()); // System.String
Console.WriteLine(typeof(string)); // System.String

Разбор:

  • obj is string проверяет совместимость конкретного объекта с типом string.
  • obj.GetType() возвращает фактический runtime-тип конкретного экземпляра.
  • typeof(string) возвращает тип на этапе компиляции (метаинформация о самом типе string).
  • Сравнение GetType() и typeof(...) помогает понимать разницу между "тип переменной" и "тип объекта в памяти".

var

В C# предусмотрен и ещё один удобный вариант неявной типизации. Это объявление переменных через var, который не отключает типизацию, а лишь позволяет компилятору определить тип на основе правой части. После компиляции var исчезает — остаётся конкретный тип.

Когда тип очевиден и длинный, спокойно используйте слово var. Фактически это статическая типизация, просто без явного написания имени типа. var – неявно типизированная переменная – автоматически определяет тип:

var age = 30; // int
var name = "Alice"; // string
var list = new List<int>(); // List<int>

Разбор:

  • var не делает язык динамическим: компилятор всё равно фиксирует конкретный тип каждой переменной.
  • age выводится как int, name как string, list как List<int>.
  • После вывода типа нельзя присвоить переменной значение другого несовместимого типа.
  • var повышает читаемость, когда тип очевиден из правой части выражения.

dynamic

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

dynamic obj = "Привет";
Console.WriteLine(obj.Length); // OK

obj = 42;
Console.WriteLine(obj + 10); // 52

Разбор:

  • dynamic откладывает проверку членов и операций до выполнения программы.
  • В первой части obj содержит строку, поэтому обращение к Length успешно.
  • Затем в obj записывается int, и выражение obj + 10 трактуется как числовое сложение.
  • Цена гибкости: ошибки обнаруживаются поздно (в runtime), поэтому dynamic лучше ограничивать интеграционными сценариями.

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


COM

COM (Component Object Model) — это старая технология Microsoft (с 1990-х), которая позволяет разным приложениям и компонентам взаимодействовать между собой, даже если они написаны на разных языках (например, C++, visual-basic, Delphi).

Это Excel, Outlook, Active Directory, и хоть и устаревшая, но всё ещё использующаяся в корпоративных приложениях с Excel/Word.

COM-объект — это компонент Windows, с которым можно взаимодействовать из C#. dynamic подойдёт для него, потому что типы и методы разрешаются в рантайме, а не на этапе компиляции.