Яка різниця між кастингом та примусом?


85

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

Чи є, можливо, чіткий і простий спосіб пояснити різницю, про яку ви, хлопці, знаєте?

Перетворення типу (також іноді відоме як типовий привід )

Використовувати значення одного типу в контексті, який очікує іншого.

Неконвертуюча акторська команда (іноді її називають каламбуром типу )

Зміна, яка не змінює основні біти.

Примус

Процес, за допомогою якого компілятор автоматично перетворює значення одного типу у значення іншого типу, коли цей другий тип вимагається оточуючим контекстом.


Відповіді:


114

Перетворення типу :

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

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

Слово ` ` кидок '', як правило, відноситься до явного перетворення типу (на відміну від неявного перетворення), незалежно від того, є це повторна інтерпретація бітового шаблону або реальне перетворення.

Отже, примус є неявним, актори - явними, а перетворення - це будь-який з них.


Кілька прикладів (з того ж джерела ):

Примус (неявний):

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

Акторський склад (явний):

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, оскільки втрачаєте точність. З примусом типу це не є "проблемою".
Максим Руйлер

Ця відповідь не узгоджується зі специфікаціями, визначеними в ecma 335 для CIL. Я виклав визначення специфікації на прикладах у своїй відповіді.
P.Brian.Mackey

24

Як ви зазначаєте, варіанти використання різняться.

Мої особисті звички:

  • "Акторський склад" - це використання оператора акторського складу . Оператор приведення вказує компілятору, що (1) цей вираз, як відомо, не є даним типом, але я обіцяю вам, що значення буде таким типом під час виконання; компілятор повинен обробляти вираз як даний тип, і час виконання видасть помилку, якщо це не так, або (2) вираз цілком іншого типу, але є добре відомий спосіб асоціювати екземпляри типу виразу з екземплярами типу, що передається. Компілятору доручається генерувати код, який виконує перетворення. Уважний читач зауважить, що це протилежності, що, на мою думку, є хитрим трюком.

  • "Перетворення" - це операція, за допомогою якої значення одного типу розглядається як значення іншого типу - як правило, іншого типу, хоча "перетворення ідентичності" все ще є перетворенням, технічно кажучи. Перетворення може бути "зміною репрезентації", як int на double, або це може бути "збереження репрезентації", як рядок на об'єкт. Перетворення можуть бути "неявними", для яких не потрібний акторський склад, або "явними", які вимагають акторського складу.

  • "Примус" - це неявне перетворення, що змінює репрезентацію.


1
Я думаю, що перше речення цієї відповіді - найважливіше з усіх. Різні мови використовують ці терміни, щоб означати зовсім різні речі. Наприклад, у Хаскелі "примус" ніколи не змінює уявлення; безпечний примус, Data.Coerce.coerce :: Coercible a b => a -> bпрацює для типів, які мають однакові показники; Unsafe.Coerce.unsafeCoerce :: a -> bпрацює для будь-яких двох типів (і змусить демонів виходити з носа, якщо ви використовуєте це неправильно).
dfeuer

@dfeuer цікавий пункт даних, дякую! Зауважу, що специфікація C # не визначає "примус"; моя пропозиція - це саме те, що я особисто маю на увазі. Враховуючи, що цей термін здається погано визначеним, я взагалі уникаю його.
Ерік Ліпперт,

8

Кастинг - це процес, за допомогою якого ви розглядаєте тип об’єкта як інший тип, примус - це перетворення одного об’єкта в інший.

Зверніть увагу, що в попередньому процесі не відбувається перетворення, у вас є тип, який ви хотіли б розглядати як інший, скажімо, наприклад, у вас є 3 різні об'єкти, які успадковуються від базового типу, і у вас є метод, який прийме це базового типу, у будь-який момент, якщо ви знаєте конкретний дочірній тип, ви можете ПРИЛОЖИТИ його до того, що він є, і використовувати всі конкретні методи та властивості цього об’єкта, і це не створить нового екземпляра об’єкта.

З іншого боку, примус передбачає створення нового об’єкта в пам’яті нового типу, а потім оригінальний тип буде скопійований до нового, залишаючи обидва об’єкти в пам’яті (до тих пір, поки збірщики сміття не заберуть або обох) .

Як приклад розглянемо такий код:

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 неявно відливається на базу, тобто немає створення нового об'єкта, оскільки obj2 вже може бути baseClass
  • obj3 потрібно якось перетворити на base, вам потрібно буде надати власний метод перетворення з otherClassto baseClass, який передбачатиме створення нового об'єкта типу baseClass та заповнення його шляхом копіювання даних з obj3.

Хорошим прикладом є клас Convert C #, де він надає власний код для перетворення між різними типами.


3
Приклад може допомогти прояснити різницю, яку ви намагаєтесь зробити.
Олівер Чарльзворт

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() {}
    }

Джерело: Анотований стандарт спільної мовної інфраструктури Джеймс С. Міллер

Зараз дивним є те, що документація корпорації Майкрософт щодо 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;
}

На рівні ІЛ вони досить різні:

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 () та оператор == () у класі 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.