Даже если вы можете рассматривать их как эквивалентные, они совершенно разные по назначению. Давайте сначала попробуем определить, что такое приведение:
Приведение - это действие по изменению объекта одного типа данных на другой.
Это немного общий характер и в какой-то мере эквивалентен преобразованию, потому что приведение часто имеет тот же синтаксис преобразования, поэтому вопрос должен заключаться в том, когда преобразование (неявное или явное) разрешено языком и когда вам нужно использовать ( подробнее) явное преобразование?
Позвольте мне сначала провести простую линию между ними. Формально (даже если это эквивалентно синтаксису языка) преобразование изменит тип, в то время как преобразование изменит / может изменить значение (в конечном итоге вместе с типом). Также приведение обратимо, а преобразование - нет.
Эта тема довольно обширна, поэтому давайте попробуем немного сузить ее, исключив из игры пользовательские операторы приведения.
Неявные приведения
В C # приведение неявно, если вы не потеряете никакой информации (обратите внимание, что эта проверка выполняется с типами, а не с их фактическими значениями ).
Примитивные типы
Например:
int tinyInteger = 10;
long bigInteger = tinyInteger;
float tinyReal = 10.0f;
double bigReal = tinyReal;
Эти приведения являются неявными, потому что во время преобразования вы не потеряете никакой информации (вы просто сделаете тип шире). И наоборот, неявное приведение типов недопустимо, потому что, независимо от их фактических значений (потому что они могут быть проверены только во время выполнения), во время преобразования вы можете потерять некоторую информацию. Например, этот код не будет компилироваться, потому что a 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
s и примитивных типов, потому что с приведением вы создаете новый объект другого типа ), какие изменения - это ваше представление о них.
Явные приведения
Приведение является явным, когда преобразование не выполняется неявно компилятором, а затем необходимо использовать оператор приведения. Обычно это означает, что:
- Вы можете потерять информацию или данные, поэтому вы должны знать об этом.
- Преобразование может завершиться неудачно (потому что вы не можете преобразовать один тип в другой), поэтому, опять же, вы должны знать, что делаете.
Примитивные типы
Явное приведение требуется для примитивных типов, когда во время преобразования вы можете потерять некоторые данные, например:
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;
Это не будет компилироваться, потому что компилятор не может преобразовать текст в числа. Текст может содержать любые символы, а не только цифры, и в C # это слишком много, даже для явного приведения (но это может быть разрешено на другом языке).
Объекты
Преобразования из указателей (в объекты) могут завершиться ошибкой, если типы не связаны, например, этот код не будет компилироваться (потому что компилятор знает, что возможное преобразование невозможно):
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
реализациями на самом деле, потому что в C # с приведением вы говорите компилятору:
поверьте мне, этот тип - тот тип, даже если вы не можете этого знать сейчас, позвольте мне сделать это, и вы увидите.
-или-
не волнуйтесь, меня не волнует, что при этом преобразовании что-то будет потеряно.
Для всего остального требуется более явная операция (подумайте о последствиях простых приведений , поэтому C ++ ввел для них длинный, подробный и явный синтаксис). Это может потребовать сложной операции (для string
-> double
преобразования потребуется синтаксический анализ). Преобразование string
, например, в всегда возможно (с помощью ToString()
метода), но оно может означать нечто иное, чем вы ожидаете, поэтому оно должно быть более явным, чем приведение (чем больше вы пишете, тем больше думаете о том, что делаете ).
Это преобразование может быть выполнено внутри объекта (используя для этого известные инструкции IL), используя пользовательские операторы преобразования (определенные в классе для приведения) или более сложные механизмы ( TypeConverter
например, методы или методы класса). Вы не знаете, что произойдет с этим, но вы знаете, что это может потерпеть неудачу (поэтому IMO, когда возможно более контролируемое преобразование, вы должны его использовать). В вашем случае преобразование просто проанализирует, string
чтобы создать double
:
double value = Double.Parse(aStringVariable);
Конечно, это может потерпеть неудачу, поэтому, если вы это сделаете, вы всегда должны перехватить исключение, которое оно может выбросить ( FormatException
). Здесь это не по теме, но когда TryParse
доступен, вы должны использовать его (потому что семантически вы говорите, что это может быть не число, и это еще быстрее ... потерпеть неудачу).
Конверсии в .NET могут происходить из множества мест, TypeConverter
неявных / явных приведений с определенными пользователем операторами преобразования, реализации IConvertible
и методов анализа (я что-то забыл?). Загляните в MSDN, чтобы узнать о них подробнее.
Чтобы закончить этот длинный ответ, несколько слов об определяемых пользователем операторах преобразования. Это просто сахар, позволяющий программисту использовать приведение для преобразования одного типа в другой. Это метод внутри класса (тот, который будет приведен), который говорит: «Эй, если он / она хочет преобразовать этот тип в этот тип, я могу это сделать». Например:
float? maybe = 10;
float sure1 = (float)maybe;
float sure2 = maybe.Value;
В этом случае это явно, потому что это может потерпеть неудачу, но это разрешено реализации (даже если есть рекомендации по этому поводу). Представьте, что вы пишете собственный строковый класс следующим образом:
EasyString text = "123";
double value = (string)text;
В своей реализации вы можете решить «облегчить жизнь программисту» и раскрыть это преобразование через приведение (помните, что это просто ярлык, чтобы писать меньше). Некоторые языки могут даже допускать это:
double value = "123";
Разрешение неявного преобразования в любой тип (проверка будет выполняться во время выполнения). С соответствующими параметрами это можно сделать, например, в VB.NET. Это просто другая философия.
Что мне с ними делать?
Итак, последний вопрос: когда использовать тот или иной? Посмотрим, когда можно использовать явное приведение:
- Преобразования между базовыми типами.
- Преобразования из
object
в любой другой тип (это также может включать распаковку).
- Преобразования из производного класса в базовый класс (или во реализованный интерфейс).
- Преобразования из одного типа в другой с помощью пользовательских операторов преобразования.
Может быть выполнено только первое преобразование, Convert
поэтому для остальных у вас нет выбора, и вам нужно использовать явное приведение.
Теперь посмотрим, когда можно будет использовать Convert
:
- Преобразования из любого базового типа в другой базовый тип (с некоторыми ограничениями, см. MSDN ).
- Преобразования из любого типа, который реализуется,
IConvertible
в любой другой (поддерживаемый) тип.
- Преобразования из / в
byte
массив в / из строки.
Выводы
IMO Convert
следует использовать каждый раз, когда вы знаете, что преобразование может завершиться неудачно (из-за формата, из-за диапазона или из-за того, что оно может быть неподдерживаемым), даже если такое же преобразование может быть выполнено с преобразованием (если не доступно что-то еще). Он дает понять, кто будет читать ваш код, каковы ваши намерения и что он может потерпеть неудачу (упрощая отладку).
Для всего остального вам нужно использовать приведение, выбора нет, но если доступен другой лучший метод, я предлагаю вам использовать его. В вашем примере преобразование из string
в double
- это то, что (особенно если текст поступает от пользователя) очень часто терпит неудачу, поэтому вы должны сделать его как можно более явным (более того, вы получите больше контроля над ним), например, используя TryParse
метод.
Изменить: в чем разница между ними?
Согласно обновленному вопросу и сохранению того, что я написал ранее (о том, когда вы можете использовать приведение по сравнению с тем, когда вы можете / должны использовать Convert
), последний момент, который нужно уточнить, - есть ли разница между ними (кроме того, Convert
использует IConvertible
и IFormattable
интерфейсы, чтобы он мог выполнять операции не допускается с приведениями).
Короткий ответ - да, они по-разному ведут себя . Я вижу этот Convert
класс как класс вспомогательных методов, поэтому часто он дает некоторые преимущества или немного другое поведение. Например:
double real = 1.6;
int castedInteger = (int)real;
int convertedInteger = Convert.ToInt32(real);
Совсем другое дело, правда? Приведение обрезается (это то, что мы все ожидаем), но Convert
выполняет округление до ближайшего целого числа (чего нельзя ожидать, если вы этого не знаете). Каждый метод преобразования имеет различия, поэтому общее правило не может применяться, и их нужно рассматривать в каждом конкретном случае ... 19 базовых типов для преобразования в любой другой тип ... список может быть довольно длинным, гораздо лучше проконсультироваться с случаем MSDN, кейс!