Преобразование типов и система типизации
Практический алгоритм выбора преобразования
- Если источник ненадёжный (ввод пользователя, файл, API) - используйте
TryParse. - Если преобразование гарантировано по контракту - допустим явный cast.
- Если работаете с бизнес-логикой - избегайте
dynamicбез острой необходимости. - Если тип может быть разным - сначала
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 подойдёт для него, потому что типы и методы разрешаются в рантайме, а не на этапе компиляции.