Разница между литьем и использованием метода Convert.To()

У меня есть функция, которая задает значения double on string.

string variable = "5.00"; 

double varDouble = (double)variable;

Изменено изменение кода, и проект строится с ошибкой: System.InvalidCastException: Specified cast is not valid.

Однако после выполнения следующего...

string variable = "5.00"; 

double varDouble = Convert.ToDouble(variable);

... проект строится без ошибок.

В чем разница между кастингом и использованием метода Convert.To()? Почему кастинг бросает Exception и с помощью Convert.To() нет?

Ответ 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 реализаций на самом деле, потому что в С# с актом вы говорите компилятору:

Поверьте мне, этот тип - это тот тип, даже если вы не можете это знать сейчас, позвольте мне это сделать, и вы увидите.

-или -

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

Для чего-то еще нужна более явная операция (подумайте о последствиях легкостей, почему С++ вводит для них длинный, подробный и явный синтаксис). Это может включать сложную операцию (для преобразования stringdouble потребуется синтаксический анализ). Например, преобразование в 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 случай!

Ответ 2

Кастинг - это способ сообщить компилятору: "Я знаю, что вы думаете, что эта переменная является баром, но я знаю больше, чем вы, объект на самом деле является Foo, поэтому позвольте мне рассматривать его так, как если бы он был Foo отныне". Затем, во время выполнения, если фактический объект оказался действительно Foo, тогда ваш код работает, если окажется, что объект вообще не был Foo, тогда вы получаете исключение. (В частности, System.InvalidCastException.)

Преобразование с другой стороны - это способ сказать: "Если вы даете мне объект типа Bar, я могу создать совершенно новый объект Foo, который представляет то, что находится в этом объекте Bar.Я не буду изменять оригинальный объект, он не будет обрабатывать исходный объект по-разному, он создаст что-то новое, основанное только на каком-то другом значении. Что касается того, как он это сделает, это может быть что угодно. В случае Convert.ToDouble он вызовет вызов Double.Parse, который имеет всевозможную сложную логику для определения того, какие типы строк представляют собой числовые значения. Вы могли бы написать свой собственный метод преобразования, который сопоставлял бы строки с удвоением по-разному (возможно, для поддержки совершенно другого соглашения для отображения чисел, таких как римские цифры или что-то). Преобразование может сделать что угодно, но идея в том, что вы на самом деле не просите компилятор что-либо сделать для вас, вы тот, кто пишет код, чтобы определить, как создать новый объект, потому что компилятор без вашей помощи, не имеет способа узнать, как отображать ( в качестве примера) a string до a double.

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

Ответ 3

Метод Convert.Double фактически просто внутренне вызывает метод Double.Parse(string).

Ни тип String, ни тип Double не определяют явное/неявное преобразование между двумя типами, поэтому кастинг всегда будет терпеть неудачу.

Метод Double.Parse будет рассматривать каждый символ в String и строить числовое значение, основанное на значениях символов в String. Если какой-либо из символов недействителен, метод Parse завершается с ошибкой (также вызывает сбой метода Convert.Double).

Ответ 4

В вашем примере вы пытаетесь применить строку к двойному (не целочисленному типу).

Для этого требуется явное преобразование.

И я должен указать, что вы могли бы использовать Convert.ToDouble вместо Convert.ToInt64, так как вы можете потерять дробные части двойного значения при преобразовании в int.

если ваша переменная имеет значение "5.25", varDouble было бы 5.00 (потеря 0.25 из-за конверсии в Int64)

Чтобы ответить на вопрос о кастинге и конвертировании.

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

Перейдите на страницу Страница MSDN для правил литья/конверсий

Ответ 5

Кастинг не предполагает никакого преобразования, т.е. внутреннее представление значения не изменяется. Пример:

object o = "Hello"; // o is typed as object and contains a string.
string s = (string)o; // This works only if o really contains a string or null.

Вы можете преобразовать double в string, как этот

double d = 5;
string s = d.ToString(); // -> "5"

// Or by specifying a format
string formatted = d.ToString("N2"); // -> "5.00"

Вы можете преобразовать string в double несколькими способами (здесь только два из них):

string s = "5";
double d = Double.Parse(s); // Throws an exception if s does not contain a valid number

Или безопасный способ

string s = "5";
double d;
if (Double.TryParse(s, out d)) {
    Console.WriteLine("OK. Result = {0}", d);
} else {
    Console.WriteLine("oops!");
}

Ответ 6

Из MSDN:

Явные преобразования (трансляции): для явных преобразований требуется оператор трансляции. Кастинг требуется , когда информация может быть потеряна при преобразовании или когда преобразование может не удаться по другим причинам. Типичные примеры включают числовое преобразование в тип с меньшей точностью или меньшим диапазоном и преобразование экземпляра базового класса в производный класс.

Рассмотрим следующий пример:

double a = 2548.3;
int b;
b = (int)a; //2548 --> information (.3) lost in the conversion

А также:

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

Вы можете использовать класс System.Convert, если хотите конвертировать между несовместимыми типами. Основное отличие между литьем и преобразованием - это компиляция и время выполнения. Исключения преобразования типов отображаются в время выполнения, то есть при выполнении типа, который не выполняется во время выполнения, будет вызываться InvalidCastException.


Вывод:. При кастинге вы сообщаете компилятору, что a действительно набирает b, и если это так, то проект строится без ошибок, подобных этому примеру:
double s = 2;
int a = (int) s;

Но в преобразовании вы говорите компилятору, что есть способ создать новый объект из a типа b, пожалуйста, сделайте это и постройте проект без каких-либо ошибок, но, как я уже сказал, время выполнения, это вызовет вызов InvalidCastException.

Например, код ниже никогда не компилируется, потому что компилятор обнаруживает, что не может выразить выражение типа DateTime для ввода int:

DateTime s = DateTime.Now;
int a = (int)(s);

Но этот файл скомпилирован успешно:

DateTime s = DateTime.Now;
int a = Convert.ToInt32(s);

Но во время выполнения вы получите InvalidCastException, в котором говорится:

Недействительный перевод из 'DateTime' в 'Int32'.

Ответ 7

string variable = "5.00";     
double varDouble = (double)variable;

Вышеперечисление просто запрещено на языке. Вот список явных приемов для числовых типов: http://msdn.microsoft.com/en-us/library/yht2cx7b.aspx Как вы можете видеть, даже не каждый численный тип может быть преобразован в другой численный тип

Дополнительная информация о литье здесь

И как это отличается от Convert.ToDouble()?

При создании типа структура данных не изменяется. Ну, в случае преобразования числовых значений вы можете потерять несколько бит или получить несколько дополнительных 0 бит. Но вы все еще работаете с числом.. Вы просто меняете объем памяти, занимаемый этим числом. Это достаточно безопасно, чтобы компилятор делал все необходимое.

Но когда вы пытаетесь наложить строку на число, вы не можете этого сделать, потому что этого недостаточно, чтобы изменить объем памяти, полученный переменной. Например, 5,00 как строка представляет собой последовательность "чисел": 53 (5) 46 (.) 48 (0) 48 (0) - это для ASCII, но строка будет содержать нечто похожее, Если компилятор просто возьмет сначала N (4 для двухзначных) байт из строки - эта часть будет содержать совершенно другой двойной номер. В то же время Convert.ToDouble() запускает специальный алгоритм, который принимает каждый символ строки, выставляет цифру, которую он представляет, и делает для вас двойной номер, если строка представляет число. Языки, подобные PHP, грубо говоря, вызовут Convert.ToDouble для вас в фоновом режиме. Но С#, как статически типизированный язык, не сделает этого для вас. Это позволяет вам быть уверенным, что любая операция безопасна по типу, и вы не получите что-то неожиданное, делая что-то вроде:

double d = (double)"zzzz"

Ответ 8

Приведение строки в двойную строку не допускается С#, поэтому вы получаете исключение, вам нужно преобразовать строку (MSDN doc, который показывает приемлемые пути преобразования). Это просто потому, что строка не обязательно будет содержать числовые данные, но различные числовые типы (запрет нулевых значений). A Convert будет запускать метод, который проверяет строку, чтобы увидеть, можно ли ее преобразовать в числовое значение. Если это возможно, тогда оно вернет это значение. Если он не может, он выдает исключение.

Чтобы преобразовать его, у вас есть несколько вариантов. Вы использовали метод Convert в своем вопросе, там Parse, который во многом похож на Convert, но вы также должны посмотреть TryParse что позволит вам сделать:

string variable = "5.00"; 

double varDouble;

if (Double.TryParse(variable, out varDouble)) {
    //Code that runs if the conversion succeeded.
} else {
    //Code that runs if the conversion failed.
}

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

Ответ 9

double varDouble = (double)variable предполагает, что variable уже является двойным. Если variable не является двойным (это строка), это не сработает. double varDouble = Convert.ToDouble(variable) действительно нравится - он преобразуется. Если он может анализировать или иным образом извлекать double из variable, тогда он будет.

Во-вторых, используя Double.Parse или Double.TryParse, потому что он более четко указывает, что должно происходить. Вы начинаете со строки и ожидаете, что она будет конвертируемой в двойную. Если есть какие-либо сомнения, используйте TryParse.

Если variable - аргумент метода, измените тип на double. Сделать вызывающего абонента ответственным за предоставление правильного типа. Таким образом, компилятор выполнит эту работу для вас.