В чем разница между кастингом и принуждением?


86

Я видел, как оба термина используются почти взаимозаменяемо в различных онлайн-объяснениях, и в большинстве учебников, к которым я обращался, также не совсем ясно различие.

Может быть, есть ясный и простой способ объяснить разницу, о которой вы, ребята, знаете?

Преобразование типа (также иногда называемое приведением типа )

Использовать значение одного типа в контексте, ожидающем другого.

Неконвертирующее приведение типа (иногда известное как каламбур типа )

Изменение, которое не изменяет базовые биты.

Принуждение

Процесс, с помощью которого компилятор автоматически преобразует значение одного типа в значение другого типа, когда этот второй тип требуется для окружающего контекста.


Ответы:


115

Преобразование типов :

Слово преобразования относится к явно или неявно изменения значения из одного типа данных в другой, например , 16-битовое целое число в 32-битовое целое число.

Слово принуждение используется для обозначения неявного преобразования.

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

Итак, приведение является неявным, приведение - явным, а преобразование - любым из них.


Несколько примеров (из того же источника ):

Принуждение (неявное):

double  d;
int     i;
if (d > i)      d = i;

Cast (явный):

double da = 3.3;
double db = 3.3;
double dc = 3.4;
int result = (int)da + (int)db + (int)dc; //result == 9

сделает ли это "неявное принуждение" излишним? примечание здесь используется как «неявное принуждение» и «явное принуждение»
Дэйв Кузино

1
Неявное преобразование может быть выполнено только в том случае, если вы не теряете точность или не имеете смысла (например: Int -> double). На большинстве современных языков вы не можете использовать double-> int, потому что вы потеряете точность. При приведении типов это не «проблема».
Максим Руиллер,

1
Этот ответ не согласуется со спецификациями, определенными в ecma 335 для CIL. В своем ответе я изложил определение спецификации с примерами.
P.Brian.Mackey

24

Как вы заметили, способы использования различаются.

Мое личное использование:

  • «Приведение» - это использование оператора приведения . Оператор приведения указывает компилятору, что либо (1) это выражение неизвестно, относится к данному типу, но я обещаю вам, что значение будет иметь этот тип во время выполнения; компилятор должен рассматривать выражение как имеющее данный тип, и среда выполнения выдаст ошибку, если это не так, или (2) выражение имеет совершенно другой тип, но существует хорошо известный способ связать экземпляры типа выражения с экземплярами преобразуемого типа. Компилятору предлагается сгенерировать код, выполняющий преобразование. Внимательный читатель заметит, что это противоположности, и я думаю, что это изящный трюк.

  • «Преобразование» - это операция, при которой значение одного типа обрабатывается как значение другого типа - обычно другого типа, хотя «преобразование идентичности» по-прежнему является преобразованием с технической точки зрения. Преобразование может быть «изменением представления», например int в double, или может быть «сохранением представления», например, строкой в ​​объект. Преобразования могут быть «неявными», которые не требуют приведения, или «явными», которые требуют приведения.

  • «Принуждение» - это неявное преобразование, изменяющее представление.


1
Я думаю, что первое предложение этого ответа - самое важное. В разных языках эти термины обозначают совершенно разные вещи. В Haskell, например, «принуждение» никогда не меняет представления; безопасное принуждение, Data.Coerce.coerce :: Coercible a b => a -> bработает для типов, для которых доказано одно и то же представление; Unsafe.Coerce.unsafeCoerce :: a -> bработает для любых двух типов (и заставит демонов вылезти из вашего носа, если вы его неправильно используете).
dfeuer

@dfeuer интересные данные, спасибо! Замечу, что спецификация C # не определяет «принуждение»; мое предложение как раз то, что я имею в виду. Учитывая, что термин кажется плохо определенным, я обычно избегаю его.
Эрик Липперт

9

Приведение - это процесс, с помощью которого вы рассматриваете тип объекта как другой тип, принуждение - это преобразование одного объекта в другой.

Обратите внимание, что в первом процессе нет преобразования, у вас есть тип, который вы хотели бы рассматривать как другой, скажем, например, у вас есть 3 разных объекта, которые наследуются от базового типа, и у вас есть метод, который будет принимать это базовый тип, в любой момент, если вы знаете конкретный дочерний тип, вы можете CAST его к тому, что он есть, и использовать все определенные методы и свойства этого объекта, и это не создаст новый экземпляр объекта.

С другой стороны, принуждение подразумевает создание нового объекта в памяти нового типа, а затем исходный тип будет скопирован в новый, оставив оба объекта в памяти (до тех пор, пока сборщики мусора не уберут один или оба) .

В качестве примера рассмотрим следующий код:

class baseClass {}
class childClass : baseClass {}
class otherClass {}

public void doSomethingWithBase(baseClass item) {}

public void mainMethod()
{
    var obj1 = new baseClass();
    var obj2 = new childClass();
    var obj3 = new otherClass();

    doSomethingWithBase(obj1); //not a problem, obj1 is already of type baseClass
    doSomethingWithBase(obj2); //not a problem, obj2 is implicitly casted to baseClass
    doSomethingWithBase(obj3); //won't compile without additional code
}
  • obj1 передается без приведения или принуждения (преобразования), потому что он уже имеет тот же тип baseClass
  • obj2 неявно приводится к base, что означает, что новый объект не создается, потому что obj2 уже может быть baseClass
  • obj3 нужно как-то преобразовать в base, вам нужно будет предоставить свой собственный метод для преобразования из otherClassв baseClass, который будет включать создание нового объекта типа baseClass и заполнение его путем копирования данных из obj3.

Хорошим примером является класс Convert C #, где он предоставляет собственный код для преобразования между различными типами.


4
Пример поможет прояснить различие, которое вы пытаетесь провести.
Оливер Чарльзуорт,

2

Приведение сохраняет тип объектов. Принуждения нет.

Принуждение принимает значение типа, который НЕ совместим с присваиванием, и преобразуется в тип, совместимый с присваиванием. Здесь я выполняю принуждение, потому Int32что НЕ наследуется от Int64... поэтому он НЕ совместим с присваиванием. Это расширяющееся принуждение (без потери данных). Расширяющееся принуждение - это неявное преобразование . Принуждение выполняет преобразование.

void Main()
{
    System.Int32 a = 100;
    System.Int64 b = a;
    b.GetType();//The type is System.Int64.  
}

Приведение позволяет рассматривать тип, как если бы он был другого типа, при этом сохраняя тип .

    void Main()
    {
        Derived d = new Derived();
        Base bb = d;
        //b.N();//INVALID.  Calls to the type Derived are not possible because bb is of type Base
        bb.GetType();//The type is Derived.  bb is still of type Derived despite not being able to call members of Test
    }

    class Base 
    {
        public void M() {}
    }

    class Derived: Base
    {
        public void N() {}
    }

Источник: аннотированный стандарт общеязыковой инфраструктуры Джеймса С. Миллера.

Что странно, так это то, что документация Microsoft по Casting не согласуется с определением спецификации ecma-335 для Casting.

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

... Это похоже на принуждение не на кастинг.

Например,

  object o = 1;
  int i = (int)o;//Explicit conversions require a cast operator
  i.GetType();//The type has been explicitly converted to System.Int32.  Object type is not preserved.  This meets the definition of Coercion not casting.

Кто знает? Может быть, Microsoft проверяет, читает ли кто-нибудь этот материал.


1

Ниже приводится пост из следующей статьи :

Различием между принуждением и кастингом часто пренебрегают. Я понимаю почему; многие языки имеют одинаковый (или аналогичный) синтаксис и терминологию для обеих операций. Некоторые языки могут даже называть любое преобразование «преобразованием», но следующее объяснение относится к концепциям CTS.

Если вы пытаетесь присвоить значение некоторого типа местоположению другого типа, вы можете сгенерировать значение нового типа, которое имеет значение, аналогичное исходному. Это принуждение. Принуждение позволяет использовать новый тип, создавая новое значение, чем-то напоминающее исходное. Некоторые приведения могут отбрасывать данные (например, преобразование int 0x12345678 в короткий 0x5678), а другие - нет (например, преобразование int 0x00000008 в короткое 0x0008 или длинное 0x0000000000000008).

Напомним, что значения могут иметь несколько типов. Если ваша ситуация немного отличается, и вы хотите выбрать только другой тип значения, приведение типов - это инструмент для работы. Приведение просто указывает, что вы хотите работать с конкретным типом, который включает значение.

Разница на уровне кода варьируется от C # до IL. В C # и приведение, и приведение выглядят довольно похоже:

static void ChangeTypes(int number, System.IO.Stream stream)
{
    long longNumber = number;
    short shortNumber = (short)number;

    IDisposable disposableStream = stream;
    System.IO.FileStream fileStream = (System.IO.FileStream)stream;
}

На уровне IL они совсем другие:

ldarg.0
 conv.i8
 stloc.0

ldarg.0
 conv.i2
 stloc.1


ldarg.1
 stloc.2

ldarg.1
 castclass [mscorlib]System.IO.FileStream
 stloc.3

Что касается логического уровня, есть несколько важных отличий. Что наиболее важно помнить, так это то, что принуждение создает новое значение, а приведение - нет. Идентичность исходного значения и значение после преобразования одинаковы, а идентичность приведенного значения отличается от исходного значения; приведение создает новый отдельный экземпляр, а приведение - нет. Следствием этого является то, что результат преобразования и оригинал всегда будут эквивалентны (как по идентичности, так и по равенству), но приведенное значение может быть равно или не совпадать с оригиналом и никогда не разделяет исходную идентичность.

В приведенных выше примерах легко увидеть последствия принуждения, поскольку числовые типы всегда копируются по значению. Когда вы работаете со ссылочными типами, все становится немного сложнее.

class Name : Tuple<string, string>
{
    public Name(string first, string last)
        : base(first, last)
    {
    }

    public static implicit operator string[](Name name)
    {
        return new string[] { name.Item1, name.Item2 };
    }
}

В приведенном ниже примере одно преобразование - это приведение, а другое - приведение.

Tuple<string, string> tuple = name;
string[] strings = name;

После этих преобразований кортеж и имя равны, но строки не равны ни одному из них. Вы можете немного улучшить ситуацию (или немного запутать), реализовав Equals () и operator == () в классе Name для сравнения Name и строки []. Эти операторы «исправят» проблему сравнения, но у вас все равно останется два отдельных экземпляра; любая модификация строк не будет отражена в имени или кортеже, в то время как изменения одного из имени или кортежа будут отражаться в имени и кортеже, но не в строках.

Хотя приведенный выше пример предназначен для иллюстрации некоторых различий между приведением типов и приведением, он также служит отличным примером того, почему вы должны быть крайне осторожны при использовании операторов преобразования со ссылочными типами в C #.


1

Из стандарта CLI :

I.8.3.2 Принуждение

Иногда желательно взять значение типа, который не может быть назначен местоположению, и преобразовать значение в тип, который может быть назначен типу местоположения. Это достигается путем принуждения ценности. Принуждение принимает значение определенного типа и желаемого типа и пытается создать значение желаемого типа, которое имеет значение, эквивалентное исходному значению. Принуждение может привести как к изменению представления, так и к смене типа; следовательно, принуждение не обязательно сохраняет идентичность объекта.

Есть два вида принуждения: расширение , при котором информация никогда не теряется , и сужение , при котором информация может быть потеряна. Примером расширяющегося принуждения может быть принуждение значения, которое является 32-битным целым числом со знаком, к значению, которое является 64-битным целым числом со знаком. Примером сужающего принуждения является обратное: приведение 64-битного целого числа со знаком к 32-битному целому числу со знаком. Языки программирования часто реализуют расширяющее приведение как неявное преобразование , тогда как сужающее приведение обычно требует явного преобразования .

Некоторое принуждение встроено непосредственно в операции VES над встроенными типами (см. §I.12.1). Любое другое принуждение должно быть явно запрошено. Для встроенных типов CTS предоставляет операции для выполнения расширяющих приведений без проверок во время выполнения и сужающие приведения с проверками или усечением во время выполнения в соответствии с семантикой операции.

I.8.3.3 Литье

Поскольку значение может относиться к нескольким типам, использование значения должно четко определять, какой из его типов используется. Поскольку значения считываются из введенных ячеек, тип используемого значения является типом местоположения, из которого было прочитано значение. Если другой тип должен быть использован, значение отливать к одному из других его типов. Приведение обычно выполняется во время компиляции, но если компилятор не может статически знать, что значение имеет целевой тип, выполняется проверка приведения во время выполнения. В отличие от принуждения, приведение никогда не изменяет фактический тип объекта и не меняет его представление. Кастинг сохраняет индивидуальность объектов.

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


1

Согласно Википедии,

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

Разница между приведением типов и приведением типов заключается в следующем:

           TYPE CASTING           |                   TYPE COERCION
                                  |
1. Explicit i.e., done by user    | 1. Implicit i.e., done by the compiler
                                  |
2. Types:                         | 2. Type:
    Static (done at compile time) |     Widening (conversion to higher data 
                                  |     type)
    Dynamic (done at run time)    |     Narrowing (conversion to lower data 
                                  |     type)
                                  |
3. Casting never changes the      | 3. Coercion can result in representation 
   the actual type of object      |    as well as type change.
   nor representation.            |

Примечание . Трансляция - это не преобразование. Это просто процесс, с помощью которого мы рассматриваем тип объекта как другой тип. Следовательно, фактический тип объекта, а также представление не изменяется во время литья.

Я согласен со словами @ PedroC88:

С другой стороны, принуждение подразумевает создание нового объекта в памяти нового типа, а затем исходный тип будет скопирован в новый, оставив оба объекта в памяти (до тех пор, пока сборщики мусора не уберут один или оба) .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.