Ответ 1
Даже если вы можете увидеть их как-то как эквивалентные, они совершенно разные по назначению. Сначала попробуйте определить, что такое cast:
Кастинг - это действие по изменению объекта одного типа данных на другой.
Это немного обобщенно, и это как-то эквивалентно преобразованию, потому что у каста есть тот же синтаксис преобразования, поэтому вопрос должен быть , когда язык (неявный или явный) разрешен языком и когда вам нужно использовать (более) явное преобразование?
Позвольте мне сначала нарисовать простую строку между ними, формально (даже если это эквивалентно синтаксису языка), приведение будет изменено, тогда как преобразование будет/может изменять значение (в конечном итоге вместе с типом). Также литье является обратимым, в то время как преобразование не может быть.
Тема довольно vaste, попробуйте немного сузить, сначала мы исключаем из игры пользовательские литые операторы.
Неявные отбрасывания
В С# листинг неявный, когда вы не потеряете информацию (учтите, что эта проверка выполняется с типами, а не с их фактическими значениями).
Примитивные типы
Например:
int tinyInteger = 10;
long bigInteger = tinyInteger;
float tinyReal = 10.0f;
double bigReal = tinyReal;
Эти приведения неявны, потому что во время преобразования вы не потеряете никакой информации (вы просто делаете тип более широким). И наоборот, неявное использование не допускается, потому что, независимо от их фактических значений (поскольку они могут быть проверены только во время выполнения), во время преобразования вы можете потерять некоторую информацию. Например, этот код не будет компилироваться, потому что double
может содержать (и фактически он) значение, не представляемое с помощью float
:
double bigReal = Double.MaxValue;
float tinyReal = bigReal;
Объекты
В случае объекта (указатель на) приведение всегда подразумевается, когда компилятор может быть уверен, что тип источника является производным классом (или он реализует) тип целевого класса, например:
string text = "123";
IFormattable formattable = text;
NotSupportedException derivedException = new NotSupportedException();
Exception baseException = derivedException;
В этом случае компилятор знает, что string
реализует IFormattable
и что NotSupportedException
(происходит от) Exception
, поэтому приведение неявно. Никакая информация не теряется, потому что объекты не меняют свои типы (это отличается от struct
и примитивных типов, потому что при создании вы создаете новый объект другого типа), какие изменения вы видите их.
Явные приведения
Приведение явно, когда преобразование не выполняется неявно компилятором, а затем вы должны использовать оператор трансляции. Обычно это означает, что:
- Вы можете потерять информацию или данные, чтобы вы знали об этом.
- Конверсия может завершиться неудачно (потому что вы не можете преобразовать один тип в другой), так что, опять же, вы должны знать, что делаете.
Примитивные типы
Явный приведение требуется для примитивных типов, когда во время преобразования вы можете потерять некоторые данные, например:
double precise = Math.Cos(Math.PI * 1.23456) / Math.Sin(1.23456);
float coarse = (float)precise;
float epsilon = (float)Double.Epsilon;
В обоих примерах, даже если значения попадают в диапазон float
, вы потеряете информацию (в этом случае точность), чтобы преобразование было явным. Теперь попробуйте следующее:
float max = (float)Double.MaxValue;
Это преобразование не будет выполнено, поэтому оно должно быть явным, чтобы вы знали об этом, и вы можете сделать проверку (в примере значение является постоянным, но оно может исходить из некоторых вычислений времени выполнения или ввода-вывода), Вернемся к вашему примеру:
string text = "123";
double value = (double)text;
Это не скомпилируется, потому что компилятор не может преобразовать текст в числа. Текст может содержать любые символы, а не только цифры, и это слишком много, в С# даже для явного приведения (но это может быть разрешено на другом языке).
Объекты
Преобразование из указателей (в объекты) может завершиться неудачно, если типы не связаны друг с другом, например, этот код не будет компилироваться (поскольку компилятор знает, что невозможно преобразование):
string text = (string)AppDomain.Current;
Exception exception = (Exception)"abc";
Этот код будет скомпилирован, но он может выйти из строя во время выполнения (это зависит от эффективного типа объектов) с InvalidCastException
:
object obj = GetNextObjectFromInput();
string text = (string)obj;
obj = GetNextObjectFromInput();
Exception exception = (Exception)obj;
Конверсия
Итак, наконец, если трансляции являются преобразованиями, то зачем нужны такие классы, как Convert
? Игнорирование тонких различий, возникающих из реализации Convert
и IConvertible
реализаций на самом деле, потому что в С# с актом вы говорите компилятору:
Поверьте мне, этот тип - это тот тип, даже если вы не можете это знать сейчас, позвольте мне это сделать, и вы увидите.
-или -
не волнуйтесь, мне все равно, что в этом преобразовании будет потеряно.
Для чего-то еще нужна более явная операция (подумайте о последствиях легкостей, почему С++ вводит для них длинный, подробный и явный синтаксис). Это может включать сложную операцию (для преобразования string
→ double
потребуется синтаксический анализ). Например, преобразование в string
всегда возможно (с помощью метода ToString()
), но это может означать что-то отличное от того, что вы ожидаете, поэтому оно должно быть более явным, чем приведение (больше вы пишете, больше вы думаете о что вы делаете).
Это преобразование можно выполнить внутри объекта (используя для этого известные команды IL), используя пользовательские операторы преобразования (определенные в классе для трансляции) или более сложные механизмы (например, t224 > или методы класса). Вы не знаете, что произойдет, но вы знаете, что это может закончиться неудачей (почему IMO, когда возможно более контролируемое преобразование, вы должны использовать его). В вашем случае преобразование просто проанализирует string
, чтобы создать double
:
double value = Double.Parse(aStringVariable);
Конечно, это может потерпеть неудачу, поэтому, если вы это сделаете, вы всегда должны поймать исключение, которое он может бросить (FormatException
). Это из темы здесь, но если при наличии TryParse
, вы должны использовать его (потому что семантически вы говорите, что это может быть не число, а еще быстрее... сбой).
Конверсии в .NET могут исходить из множества мест, TypeConverter
, неявных/явных приемов с пользовательскими операторами преобразования, реализации IConvertible
и методов разбора (я что-то забыл?). Взгляните на MSDN для получения более подробной информации о них.
Чтобы завершить этот длинный ответ всего лишь несколько слов о пользовательских операторах преобразования. Это просто сахара, чтобы программист использовал бросок, чтобы преобразовать один тип в другой. Это метод внутри класса (тот, который будет выпущен), который говорит "эй, если он хочет преобразовать этот тип в этот тип, тогда я могу это сделать". Например:
float? maybe = 10; // Equals to Nullable<float> maybe = 10;
float sure1 = (float)maybe; // With cast
float sure2 = maybe.Value; // Without cast
В этом случае он явный, потому что он может потерпеть неудачу, но это разрешено для реализации (даже если есть рекомендации по этому поводу). Представьте, что вы пишете собственный класс строк следующим образом:
EasyString text = "123"; // Implicit from string
double value = (string)text; // Explicit to double
В вашей реализации вы можете решить "облегчить жизнь программиста" и разоблачить это преобразование с помощью броска (помните, что это просто ярлык для записи меньше). Некоторые языки могут даже допускать следующее:
double value = "123";
Разрешить неявное преобразование в любой тип (проверка будет выполняться во время выполнения). При правильных настройках это можно сделать, например, в VB.NET. Это просто другая философия.
Что мне делать с ними?
Итак, последний вопрос - когда вы должны использовать тот или иной. Давайте посмотрим, когда вы можете использовать явное приведение:
- Конверсии между базовыми типами.
- Преобразования из
object
в любой другой тип (это может также включать и распаковку). - Преобразование из производного класса в базовый класс (или в реализованный интерфейс).
- Конверсии от одного типа к другому с помощью пользовательских операторов преобразования.
Только первое преобразование может быть выполнено с помощью Convert
, так что для остальных у вас нет выбора, и вам нужно использовать явный приведение.
Посмотрите теперь, когда вы можете использовать Convert
:
- Преобразования из любого базового типа в другой базовый тип (с некоторыми ограничениями, см. MSDN).
- Преобразования из любого типа, который реализует
IConvertible
для любого другого (поддерживаемого) типа. - Преобразование из/в a
byte
массив в/из строки.
Заключение
IMO Convert
следует использовать каждый раз, когда вы знаете, что преобразование может завершиться неудачно (из-за формата, из-за диапазона или из-за того, что оно может быть неподдерживаемым), даже если одно и то же преобразование может быть выполнено с помощью трансляции (если только что-то еще доступный). В нем четко указывается, кто будет читать ваш код, каковы ваши намерения и что он может выйти из строя (упрощение отладки).
Для всего остального вам нужно использовать бросок, нет выбора, но если доступен другой лучший метод, я предлагаю вам его использовать. В вашем примере преобразование из string
в double
- это то, что (особенно если текст поступает от пользователя) очень часто терпит неудачу, поэтому вы должны сделать его максимально явным (более того, вы получите больше контроля над ним), например используя метод TryParse
.
Изменить: какая разница между ними?
В соответствии с обновленным вопросом и сохранением того, что я написал ранее (о том, когда вы можете использовать бросок по сравнению с тем, когда вы можете/должны использовать Convert
), тогда последняя точка, поясняющая, есть разница между ними (более того Convert
использует интерфейсы IConvertible
и IFormattable
, чтобы он мог выполнять операции, не разрешенные с помощью бросков).
Короткий ответ да, они ведут себя разные. Я рассматриваю класс Convert
как класс вспомогательных методов, поэтому он часто дает некоторые преимущества или немного другое поведение. Например:
double real = 1.6;
int castedInteger = (int)real; // 1
int convertedInteger = Convert.ToInt32(real); // 2
Довольно разные, не так ли? Cast truncates (это то, что мы все ожидаем), но Convert
выполняет округление до ближайшего целого числа (и это может не ожидаться, если вы не знаете об этом). Каждый метод преобразования вводит различия, поэтому общее правило не может применяться, и их следует рассматривать в каждом конкретном случае... 19 базовых типов для преобразования в любой другой тип... список может быть довольно длинным, гораздо лучше проконсультироваться с делом MSDN случай!