В чем разница между кастингом и конверсией?
Комментарии Эрика Липперта по этому вопросу привели меня в замешательство. В чем разница между приведением и преобразованием в C#?
13 ответов
Я верю в то, что Эрик пытается сказать:
Кастинг - это термин, описывающий синтаксис (отсюда и синтаксическое значение).
Конверсия - это термин, описывающий, какие действия фактически предпринимаются за кулисами (и, таким образом, смысловой смысл).
Cast-выражение используется для явного преобразования выражения в заданный тип.
А также
Выражение приведения типа (T)E, где T является типом, а E является унарным выражением, выполняет явное преобразование (§13.2) значения E в тип T.
Кажется, это подтверждается тем, что оператор приведения в синтаксисе выполняет явное преобразование.
Приведение - это способ сказать компилятору: "Объект X на самом деле типа Y, продолжайте и относитесь к нему как к такому".
Преобразование говорит: "Я знаю, что Объект X не Тип Y, но существует способ создания нового Объекта из X Типа Y, продолжайте и сделайте это".
Мне вспоминается анекдот, рассказанный Ричардом Фейнманом, где он посещает урок философии, и профессор спрашивает его: "Фейнман, вы физик, по вашему мнению, электрон - это" существенный объект "?" Итак, Фейнман задает уточняющий вопрос: "Является ли кирпич существенным объектом?" к классу. У каждого ученика есть разные ответы на этот вопрос. Они говорят, что фундаментальное абстрактное понятие "кирпичность" является существенным объектом. Нет, один конкретный, уникальный кирпич является существенным объектом. Нет, части кирпича, которые вы можете наблюдать эмпирически, являются существенным объектом. И так далее.
Что, конечно, не ответить на ваш вопрос.
Я не собираюсь проходить через все эти дюжины ответов и спорить с их авторами о том, что я действительно имел в виду. Я напишу статью в блоге на эту тему через несколько недель, и мы увидим, проливает ли это свет на этот вопрос.
Как насчет аналогии, хотя, а-ля Фейнман. Вы хотите испечь буханку бананового хлеба в субботу утром (как я делаю почти каждое субботнее утро). Итак, вы консультируетесь с "Радостью кулинарии", и в ней говорится: "Бла-бла-бла... В другой миске смешайте сухие ингредиенты..."
Очевидно, что между этой инструкцией и вашими действиями завтра утром существует тесная связь, но столь же ясно, что было бы ошибкой отождествлять инструкцию с действием. Инструкция состоит из текста. У него есть местоположение, на определенной странице. Это пунктуация. Если бы вы были на кухне, смешивая муку и пищевую соду, и кто-то спросил: "Какая у вас пунктуация сейчас?", Вы, вероятно, подумали, что это был странный вопрос. Действие связано с инструкцией, но текстовые свойства инструкции не являются свойствами действия.
Состав не является преобразованием так же, как рецепт не является актом выпечки торта. Рецепт - это текст, описывающий действие, которое вы затем можете выполнить. Оператор приведения - это текст, который описывает действие - преобразование, которое может затем выполнить среда выполнения.
Из C# Spec 14.6.6:
Cast-выражение используется для явного преобразования выражения в заданный тип.
...
Выражение приведения типа (T)E, где T является типом, а E является унарным выражением, выполняет явное преобразование (§13.2) значения E в тип T.
Таким образом, приведение является синтаксической конструкцией, используемой для указания компилятору вызывать явные преобразования.
Из C# Spec §13:
Преобразование позволяет использовать выражение одного типа как другой тип. Преобразования могут быть неявными или явными, и это определяет, требуется ли явное приведение. [Пример: Например, преобразование из типа int в тип long является неявным, поэтому выражения типа int могут неявно рассматриваться как тип long. Обратное преобразование из типа long в тип int является явным, поэтому требуется явное приведение.
Таким образом, преобразования - это то, где выполняется настоящая работа. Вы заметите, что в кавычке выражения приведения выражается, что она выполняет явные преобразования, но явные преобразования - это расширенный набор неявных преобразований, поэтому вы также можете вызывать неявные преобразования (даже если вам это не нужно) с помощью выражений приведения.
Просто мое понимание, вероятно, слишком просто:
При приведении основных данных остается нетронутым (то же самое внутреннее представление) - "Я знаю, что это словарь, но вы можете использовать его как ICollection".
При конвертации вы меняете внутреннее представление на что-то другое: "Я хочу, чтобы этот int был строкой".
После прочтения комментариев Эрика, попытка на простом английском:
Приведение означает, что два типа фактически одинаковы на некотором уровне. Они могут реализовывать один и тот же интерфейс или наследовать от одного и того же базового класса, или цель может быть "одинаковой" (надмножество?) Для работы приведения, такого как приведение от Int16 к Int32.
Преобразование типов означает, что эти два объекта могут быть достаточно похожими для преобразования. Возьмем, например, строковое представление числа. Это строка, ее нельзя просто преобразовать в число, ее необходимо проанализировать и преобразовать из одного в другое, и процесс может завершиться неудачей. Это может не сработать и на кастинге, но я думаю, что это гораздо менее дорогой сбой.
И это ключевое различие между этими двумя понятиями, я думаю. Преобразование повлечет за собой некоторый анализ или более глубокий анализ и преобразование исходных данных. Кастинг не разбирает. Он просто пытается найти совпадение на некотором полиморфном уровне.
Приведение - это создание значения одного типа из другого значения другого типа. Преобразование - это тип приведения, в котором также должно быть изменено внутреннее представление значения (а не только его интерпретация).
В C# приведение и преобразование выполняются с помощью выражения cast:
(тип) унарное выражение
Различие важно (и это делается в комментарии), потому что оператором-преобразователем-декларатором могут быть созданы только преобразования. Поэтому в коде могут быть созданы только (неявные или явные) преобразования.
Неявное приведение без преобразования всегда доступно для приведений подтипа к подтипу, а явное приведение без преобразования всегда доступно для приведений типа к подтипу. Другие неконвертирующие броски не допускаются.
В этом контексте приведение означает, что вы выставляете объект данного типа для манипуляции как некоторого другого типа, преобразование означает, что вы фактически заменяете объект данного типа на объект другого типа.
На этой странице документации MSDN C# предполагается, что приведение является конкретным примером преобразования: "явное преобразование". То есть преобразование формы x = (int)y
это актерский состав.
Автоматическое изменение типа данных (например, myLong = myInt
) являются более общим "преобразованием".
Приведение и преобразование в основном одинаковы в C#, за исключением того, что преобразование может быть выполнено с использованием любого метода, такого как Object.ToString()
, Кастинг только с оператором кастинга (T) E
, что описано в других сообщениях, и может использовать преобразования или бокс.
Какой метод конвертации он использует? Компилятор решает, основываясь на классах и библиотеках, предоставленных компилятору во время компиляции. Если существует неявное преобразование, вы не обязаны использовать оператор приведения. Object o = String.Empty
, Если существуют только явные преобразования, вы должны использовать оператор приведения. String s = (String) o
,
Вы можете создать explicit
а также implicit
операторы преобразования в ваших собственных классах. Примечание: преобразования могут сделать данные очень похожими или совсем не похожими на исходный тип для вас и меня, но все это определяется методами преобразования и делает их легальными для компилятора.
Кастинг всегда относится к использованию оператора кастинга. Ты можешь написать
Object o = float.NaN;
String s = (String) o;
Но если вы получите доступ s
Например, в Console.WriteLine
, вы получите время выполнения InvalidCastException
, Таким образом, оператор приведения все еще пытается использовать преобразование во время доступа, но согласится на бокс во время назначения.
Приведение - это оператор класса / структуры. Преобразование - это метод / процесс в одном или другом из затронутых классов / структур или может быть в совершенно другом классе / структуре (т.е. Converter.ToInt32()
Операторы приведения бывают двух видов: неявные и явные
Неявные операторы приведения указывают, что данные одного типа (скажем, Int32) всегда могут быть представлены как другой тип (десятичный) без потери данных / точности.
int i = 25;
decimal d = i;
Явные операторы приведения указывают, что данные одного типа (десятичные) всегда могут быть достоверно представлены как другой тип (int), но возможна потеря данных / точности. Поэтому компилятор требует от вас явного заявления о том, что вы знаете об этом и все равно хотите это сделать, используя явный синтаксис приведения:
decimal d = 25.0001;
int i = (int)d;
Преобразование принимает два типа, которые не обязательно связаны каким-либо образом, и пытается преобразовать один в другой посредством некоторого процесса, такого как синтаксический анализ. Если все известные алгоритмы преобразования терпят неудачу, процесс может либо вызвать исключение, либо вернуть значение по умолчанию:
string s = "200";
int i = Converter.ToInt32(s); // set i to 200 by parsing s
string s = "two hundred";
int i = Converter.ToInt32(s); // sets i to 0 because the parse fails
Ссылки Эрика на синтаксическое преобразование против символического преобразования - в основном различие между оператором и методологией.
Приведение является синтаксическим и может включать или не включать преобразование (в зависимости от типа приведения). Как вы знаете, C++ позволяет указать тип приведения, который вы хотите использовать.
Повышение / понижение иерархии может или не может считаться преобразованием, в зависимости от того, кого вы спрашиваете (и на каком языке они говорят!)
Эрик (C#) говорит, что приведение к другому типу всегда включает преобразование, хотя это преобразование может даже не изменить внутреннее представление экземпляра.
С ++- парень не согласится, так как static_cast
может не привести к какому-либо дополнительному коду (поэтому "преобразование" на самом деле не реально!)
ВСТАВЛЕНИЕ РЕДАКТИРОВАНИЯ № 2: разве это не смешно-непоследовательная близорукость, что с тех пор, как я дал этот ответ, вопрос был помечен как дубликат вопроса, который спрашивает: " Является ли приведение того же, что и обращение? ". И ответы "Нет" подавляющим большинством голосов. Тем не менее, мой ответ здесь, который указывает на генеративную сущность того, почему приведение не совпадает с преобразованием, подавляющим большинством голосов (хотя у меня есть один +1 в комментариях). Я полагаю, что читателям трудно понять, что преобразования применяются на уровне денотационного синтаксиса / семантики, а преобразования применяются на уровне операционной семантики. Например, приведение ссылки (или указателя в C/C++), относящейся к упакованному типу данных, к другому типу данных не приводит (во всех языках и сценариях) к преобразованию упакованных данных. Например, в C float a[1]; int* p = (int*)&a;
не гарантирует, что *p
относится к int
данные.
Компилятор компилируется из денотационной семантики в операционную семантику. Компиляция не является биективной, то есть не гарантируется, что она будет скомпилирована (например, Java, LLVM, asm.js или C# байт-код) обратно в любой денотационный синтаксис, который компилируется в этот байт-код (например, Scala, Python, C#, C через Emscripten, так далее). Таким образом, два слоя не совпадают.
Таким образом, наиболее очевидно, что " приведение " и " преобразование" - это не одно и то же. Мой ответ здесь указывает на то, что термины применяются к двум различным уровням семантики. Приведения применяются к семантике того, что знает денотационный слой (входной синтаксис компилятора). Преобразования применяются к семантике того, о чем знает рабочий уровень (во время выполнения или промежуточный байт-код). Я использовал стандартный термин "стереть", чтобы описать, что происходит с денотационной семантикой, которая явно не записана на уровне операционной семантики.
Например, усовершенствованные обобщения являются примером записи денотационной семантики на уровне операционной семантики, но у них есть недостаток, заключающийся в том, что уровень эксплуатационной семантики несовместим с денотационной семантикой более высокого порядка, например, поэтому было болезненно рассмотреть реализацию более высокой родовые дженерики в CLR C#, потому что денотационная семантика C# для дженериков была жестко запрограммирована на уровне операционной семантики.
Давай, ребята, перестаньте осуждать кого-то, кто знает намного больше, чем вы. Сделайте свою домашнюю работу, прежде чем голосовать.
INSERTED EDIT: приведение - это операция, которая происходит на уровне денотационной семантики (где типы выражены в их полной семантике). Приведение может (например, явное преобразование) или не может (например, преобразование с повышением частоты передачи) вызвать преобразование на семантическом уровне времени выполнения. Недостатки моего ответа (и голосование против комментария Марка Гэвина) показывают мне, что большинство людей не понимают различий между денотационной семантикой и операционной (исполнительской) семантикой. Вздох.
Я изложу ответ Эрика Липперта более просто и более широко для всех языков, включая C#.
Приведение является синтаксисом, поэтому (как и весь синтаксис) стирается во время компиляции; тогда как преобразование вызывает некоторые действия во время выполнения.
Это верное утверждение для каждого компьютерного языка, который я знаю во всей вселенной. Обратите внимание, что приведенное выше утверждение не говорит о том, что приведение и преобразование являются взаимоисключающими.
Приведение может вызвать преобразование во время выполнения, но в некоторых случаях это может не произойти.
Причина, по которой у нас есть два разных слова, то есть приведение и преобразование, заключается в том, что нам нужен способ отдельно описать то, что происходит в синтаксисе (оператор приведении) и во время выполнения (преобразование или проверка типа и возможное преобразование).
Важно поддерживать это разделение понятий, потому что в некоторых языках программирования приведение никогда не вызывает преобразование. Также, чтобы мы понимали, неявное приведение (например, апкастинг) происходит только во время компиляции. Причина, по которой я написал этот ответ, заключается в том, что я хочу помочь читателям понять, как они говорят на нескольких языках с компьютерными языками. А также, чтобы увидеть, как это общее определение правильно применяется и в случае C#.
Также я хотел помочь читателям понять, как я обобщаю концепции в своем уме, что помогает мне как разработчику компьютерного языка. Я пытаюсь передать дар очень редукционистского, абстрактного мышления. Но я также пытаюсь объяснить это очень практичным способом. Пожалуйста, не стесняйтесь сообщить мне в комментариях, если мне нужно улучшить разъяснение.
Эрик Липперт написал:
Состав не является преобразованием так же, как рецепт не является актом выпечки торта. Рецепт - это текст, описывающий действие, которое вы затем можете выполнить. Оператор приведения - это текст, который описывает действие - преобразование, которое может затем выполнить среда выполнения.
Рецепт - это то, что происходит в синтаксисе. Синтаксис всегда стирается и заменяется либо ничем, либо некоторым кодом времени выполнения.
Например, я могу написать приведение в C#, которое ничего не делает и полностью стирается во время компиляции, когда оно не вызывает изменения в требованиях к хранилищу или является устаревшим. Мы ясно видим, что приведение - это просто синтаксис, который не вносит изменений в код времени выполнения.
int x = 1;
int y = (int)x;
Giraffe g = new Giraffe();
Animal a = (Animal)g;
Это может быть использовано в целях документирования (хотя и с шумом), но это важно для языков с выводом типа, где иногда требуется преобразование, чтобы сообщить компилятору, какой тип вы хотите вывести.
Например, в Scala None
имеет тип Option[Nothing]
где Nothing
это нижний тип, который является подтипом всех возможных типов (не супертип). Поэтому иногда, когда используется None, тип должен быть приведен к определенному типу, потому что Scala делает только локальный вывод типа, поэтому не всегда может вывести тип, который вы предполагали.
// (None : Option[Int]) casts None to Option[Int]
println(Some(7) <*> ((None : Option[Int]) <*> (Some(9) > add)))
Приведение может знать во время компиляции, что оно требует преобразования типа, например int x = (int)1.5
или может потребовать проверки типа и возможного преобразования типа во время выполнения, например, при понижении частоты. Приведение (т.е. синтаксис) стирается и заменяется действием во время выполнения.
Таким образом, мы можем ясно видеть, что приравнивание всех приведений к явному преобразованию является ошибкой в документации MSDN. В этой документации предполагается, что для явного преобразования требуется оператор приведения, но не следует пытаться также подразумевать, что все приведения являются явными преобразованиями. Я уверен, что Эрик Липперт может прояснить это, когда он пишет блог, который он обещал в своем ответе.
ДОБАВЬТЕ: из комментариев и чата я вижу, что есть некоторая путаница в значении термина " удалено".
Термин "стерт" используется для описания информации, которая была известна во время компиляции, которая не известна во время выполнения. Например, типы могут быть удалены в нереализованных обобщениях, и это называется стиранием типов.
Вообще говоря, весь синтаксис стирается, потому что обычно CLI не является биективным (обратимым и взаимно-однозначным) с C#. Вы не всегда можете вернуться назад от некоторого произвольного кода CLI обратно к точному исходному коду C#. Это означает, что информация была стерта.
Те, кто говорит, что стертое не является правильным термином, связывают выполнение актерского состава с семантикой актерского состава. Приведение является семантикой более высокого уровня (я думаю, что это на самом деле выше, чем синтаксис, это денотационная семантика, по крайней мере, в случае с повышением или понижением), которая говорит на этом уровне семантики, что мы хотим привести тип. Теперь, как это делается во время выполнения, это совершенно другой уровень семантики. В некоторых языках это всегда может быть NOOP. Например, в Haskell вся информация о наборе стирается во время компиляции.