ความแตกต่างระหว่างการหล่อและการบีบบังคับคืออะไร?


86

ฉันเคยเห็นทั้งสองคำถูกใช้แทนกันในคำอธิบายออนไลน์ต่างๆและหนังสือข้อความส่วนใหญ่ที่ฉันได้ปรึกษาก็ไม่ชัดเจนเกี่ยวกับความแตกต่าง

อาจมีวิธีอธิบายความแตกต่างที่พวกคุณรู้อย่างชัดเจนและเรียบง่ายหรือไม่?

การแปลงประเภท (บางครั้งเรียกว่าประเภทแคสต์ )

เพื่อใช้ค่าประเภทหนึ่งในบริบทที่คาดหวังอีกประเภทหนึ่ง

ชนิดไม่แปลงค่า (บางครั้งเรียกว่าประเภทปุน )

การเปลี่ยนแปลงที่ไม่เปลี่ยนแปลงบิตที่อยู่เบื้องหลัง

การบีบบังคับ

กระบวนการที่คอมไพลเลอร์แปลงค่าประเภทหนึ่งเป็นค่าของประเภทอื่นโดยอัตโนมัติเมื่อบริบทรอบข้างต้องการชนิดที่สอง


4
สิ่งที่เกี่ยวกับบทความวิกิพีเดีย ?
Oliver Charlesworth

คำตอบ:


115

ประเภทการแปลง :

การแปลงคำหมายถึงการเปลี่ยนค่าจากประเภทข้อมูลหนึ่งไปยังอีกประเภทหนึ่งโดยปริยายหรือโดยชัดแจ้งเช่นจำนวนเต็ม 16 บิตเป็นจำนวนเต็ม 32 บิต

คำว่าบีบบังคับใช้เพื่อแสดงถึงการเปลี่ยนใจเลื่อมใสโดยปริยาย

โดยทั่วไปคำว่าcastจะหมายถึงการแปลงประเภทอย่างชัดเจน (ตรงข้ามกับการแปลงโดยนัย) โดยไม่คำนึงว่าจะเป็นการตีความรูปแบบบิตใหม่หรือการแปลงจริง

ดังนั้นการบีบบังคับเป็นนัยยะการโยนนั้นชัดเจนและการเปลี่ยนใจเลื่อมใสเป็นสิ่งใดสิ่งหนึ่ง


ตัวอย่างบางส่วน (จากแหล่งเดียวกัน ):

การบีบบังคับ (โดยนัย):

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

แคสต์ (Explicit):

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

สิ่งนี้จะทำให้ "การบังคับโดยนัย" ซ้ำซ้อนหรือไม่ หมายเหตุที่นี่ใช้ทั้ง "การบังคับโดยนัย" และ "การบังคับโดยนัย"
Dave Cousineau

1
การแปลงโดยนัยสามารถทำได้ก็ต่อเมื่อคุณไม่สูญเสียความแม่นยำหรือเข้าท่า (เช่น: Int -> double) ในภาษาสมัยใหม่ส่วนใหญ่คุณไม่สามารถทำ double-> int ได้เพราะคุณจะสูญเสียความแม่นยำ ด้วยการบีบบังคับแบบนั้นไม่ใช่ "ปัญหา"
Maxime Rouiller

1
คำตอบนี้ไม่สอดคล้องกับข้อกำหนดที่กำหนดไว้ใน ecma 335 สำหรับ CIL ฉันวางข้อกำหนดคุณสมบัติพร้อมตัวอย่างในคำตอบของฉัน
P. Brian.Mackey

24

การใช้งานแตกต่างกันไปตามที่คุณทราบ

การใช้งานส่วนตัวของฉันคือ:

  • "cast" คือการใช้ตัวดำเนินการแคสต์ ตัวดำเนินการ cast สั่งคอมไพเลอร์ว่า (1) ไม่ทราบว่านิพจน์นี้เป็นประเภทที่กำหนด แต่ฉันสัญญากับคุณว่าค่าจะเป็นประเภทนั้นเมื่อรันไทม์ คอมไพเลอร์ต้องปฏิบัติต่อนิพจน์ว่าเป็นประเภทที่กำหนดและรันไทม์จะสร้างข้อผิดพลาดหากไม่ใช่หรือ (2) นิพจน์เป็นประเภทที่แตกต่างกันโดยสิ้นเชิง แต่มีวิธีที่รู้จักกันดีในการเชื่อมโยงอินสแตนซ์ ของประเภทนิพจน์พร้อมอินสแตนซ์ของประเภท cast-to คอมไพเลอร์ได้รับคำสั่งให้สร้างโค้ดที่ดำเนินการแปลง ผู้อ่านที่ใส่ใจจะสังเกตว่าสิ่งเหล่านี้เป็นสิ่งที่ตรงกันข้ามซึ่งฉันคิดว่าเป็นเคล็ดลับที่เรียบร้อย

  • "Conversion" คือการดำเนินการที่มูลค่าประเภทหนึ่งถือเป็นมูลค่าของประเภทอื่นซึ่งโดยปกติจะเป็นประเภทอื่นแม้ว่า "การแปลงข้อมูลประจำตัว" จะยังคงเป็น Conversion แต่ในทางเทคนิค การแปลงอาจเป็น "การแสดงเปลี่ยน" เช่น int เป็นสองเท่าหรืออาจเป็น "การรักษาการแทน" เช่นสตริงต่อวัตถุ การแปลงอาจเป็นแบบ "โดยนัย" ซึ่งไม่จำเป็นต้องมีการแคสต์หรือ "โจ่งแจ้ง" ซึ่งต้องมีการแคสต์

  • "การบีบบังคับ" คือการแสดงถึงการเปลี่ยนใจเลื่อมใสโดยนัย


1
ฉันคิดว่าประโยคแรกของคำตอบนี้เป็นสิ่งที่สำคัญที่สุด ภาษาที่แตกต่างกันใช้คำเหล่านี้เพื่อหมายถึงสิ่งที่แตกต่างกันมาก ตัวอย่างเช่นใน Haskell "การบีบบังคับ" ไม่เคยเปลี่ยนการเป็นตัวแทน การบีบบังคับที่ปลอดภัยData.Coerce.coerce :: Coercible a b => a -> bใช้ได้กับประเภทที่พิสูจน์แล้วว่ามีการแสดงแบบเดียวกัน Unsafe.Coerce.unsafeCoerce :: a -> bใช้ได้กับสองประเภท (และจะทำให้ปีศาจออกมาจากจมูกของคุณหากคุณใช้ผิด)
dfeuer

@dfeuer จุดข้อมูลที่น่าสนใจขอบคุณ! ฉันทราบว่าข้อกำหนด C # ไม่ได้กำหนด "การบังคับ"; ข้อเสนอแนะของฉันเป็นเพียงความหมายส่วนตัวเท่านั้น เนื่องจากคำนี้ดูเหมือนจะกำหนดไว้ไม่ดีฉันจึงหลีกเลี่ยงคำนี้โดยทั่วไป
Eric Lippert

9

การแคสต์เป็นกระบวนการที่คุณถือว่าประเภทออบเจ็กต์เป็นประเภทอื่น Coercing กำลังแปลงวัตถุหนึ่งไปเป็นอีกประเภทหนึ่ง

โปรดทราบว่าในกระบวนการเดิมไม่มีการแปลงที่เกี่ยวข้องคุณมีประเภทที่คุณต้องการจะถือว่าเป็นอีกประเภทหนึ่งเช่นคุณมีวัตถุ 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 เป็นฐานคุณจะต้องระบุวิธีการของคุณเองในการแปลงจากotherClassเป็นbaseClassซึ่งจะเกี่ยวข้องกับการสร้างวัตถุใหม่ประเภท baseClass และกรอกข้อมูลโดยการคัดลอกข้อมูลจาก obj3

ตัวอย่างที่ดีคือคลาส Convert C #ซึ่งมีโค้ดที่กำหนดเองเพื่อแปลงระหว่างประเภทต่างๆ


4
ตัวอย่างจะช่วยชี้แจงความแตกต่างที่คุณพยายามทำ
Oliver Charlesworth

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

ที่มา: The Common Language Infrastructure Annotated Standard โดย James S.Miller

ตอนนี้สิ่งที่แปลกคือเอกสารของ Microsoft เกี่ยวกับการหล่อไม่สอดคล้องกับข้อกำหนดข้อกำหนดของการหล่อของ ecma-335

การแปลงที่ชัดเจน (แคสต์): การแปลงที่ชัดเจนต้องใช้ตัวดำเนินการแคสต์ จำเป็นต้องแคสต์เมื่อข้อมูลอาจสูญหายในการแปลงหรือเมื่อการแปลงอาจไม่สำเร็จด้วยเหตุผลอื่น ตัวอย่างทั่วไป ได้แก่ การแปลงตัวเลขเป็นประเภทที่มีความแม่นยำน้อยกว่าหรือช่วงที่เล็กกว่าและการแปลงอินสแตนซ์คลาสพื้นฐานเป็นคลาสที่ได้รับ

... ฟังดูเหมือนบีบบังคับไม่ใช่แคสติ้ง

ตัวอย่างเช่น,

  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

หากคุณพยายามกำหนดค่าบางประเภทให้กับตำแหน่งของประเภทอื่นคุณสามารถสร้างค่าของประเภทใหม่ที่มีความหมายใกล้เคียงกับค่าเดิม นี่คือการบีบบังคับ Coercion ช่วยให้คุณสามารถใช้รูปแบบใหม่ได้โดยการสร้างค่าใหม่ที่คล้ายคลึงกับแบบเดิม การบีบบังคับบางอย่างอาจละทิ้งข้อมูล (เช่นการแปลง 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 };
    }
}

ในตัวอย่างด้านล่างการเปลี่ยนใจเลื่อมใสหนึ่งเป็นการโยนในขณะที่อีก Conversion หนึ่งเป็นการบีบบังคับ

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

หลังจากการแปลงเหล่านี้ทูเปิลและชื่อจะเท่ากัน แต่สตริงไม่เท่ากับค่าใดค่าหนึ่ง คุณสามารถทำให้สถานการณ์ดีขึ้นเล็กน้อย (หรือสับสนขึ้นเล็กน้อย) โดยใช้ Equals () และตัวดำเนินการ == () บนคลาส Name เพื่อเปรียบเทียบชื่อและสตริง [] ตัวดำเนินการเหล่านี้จะ "แก้ไข" ปัญหาการเปรียบเทียบ แต่คุณยังคงมีสองอินสแตนซ์แยกกัน การปรับเปลี่ยนสตริงใด ๆ จะไม่แสดงในชื่อหรือทูเพิลในขณะที่การเปลี่ยนแปลงชื่อหรือทูเพิลจะแสดงในชื่อและทูเพิล แต่จะไม่แสดงในสตริง

แม้ว่าตัวอย่างข้างต้นมีวัตถุประสงค์เพื่อแสดงให้เห็นถึงความแตกต่างบางประการระหว่างการคัดเลือกนักแสดงและการบีบบังคับ แต่ก็เป็นตัวอย่างที่ดีว่าเหตุใดคุณจึงควรระมัดระวังอย่างยิ่งเกี่ยวกับการใช้ตัวดำเนินการแปลงที่มีประเภทการอ้างอิงใน C #


1

จากมาตรฐาน CLI :

I.8.3.2 การบีบบังคับ

บางครั้งเป็นที่พึงปรารถนาที่จะรับค่าของประเภทที่ไม่สามารถกำหนดให้กับตำแหน่งที่ตั้งและแปลงค่าเป็นชนิดที่กำหนดให้กับประเภทของสถานที่ตั้งได้ สิ่งนี้สามารถทำได้โดยการ บีบบังคับมูลค่า การบังคับใช้ค่าของประเภทเฉพาะและประเภทที่ต้องการและพยายามสร้างมูลค่าของประเภทที่ต้องการซึ่งมีความหมายเทียบเท่ากับค่าดั้งเดิม การบีบบังคับอาจส่งผลให้เกิดการเปลี่ยนแปลงการเป็นตัวแทนเช่นเดียวกับการเปลี่ยนแปลงประเภท ดังนั้นการบีบบังคับไม่จำเป็นต้องรักษาเอกลักษณ์ของวัตถุ

การบีบบังคับมีสองประเภท: การขยับขยายซึ่งจะไม่สูญเสียข้อมูลและการจำกัดข้อมูลให้แคบลงซึ่งข้อมูลอาจสูญหาย ตัวอย่างของการบีบบังคับในการขยับขยายจะเป็นการบังคับค่าที่เป็นจำนวนเต็มที่มีการเซ็นชื่อ 32 บิตให้เป็นค่าที่เป็นจำนวนเต็มแบบ 64 บิต ตัวอย่างของการบังคับให้แคบลงคือการย้อนกลับ: การบังคับจำนวนเต็มที่ลงชื่อ 64 บิตเป็นจำนวนเต็มที่มีลายเซ็น 32 บิต การเขียนโปรแกรมภาษามักจะดำเนินการขยับขยาย coercions เป็นแปลงโดยปริยายขณะ coercions กวดขันมักจะต้องมีการแปลงอย่างชัดเจน

การบีบบังคับบางอย่างถูกสร้างขึ้นโดยตรงในการดำเนินการ VES ในประเภทที่มีอยู่แล้วภายใน (ดู§I.12.1) การบังคับอื่น ๆ ทั้งหมดจะต้องได้รับการร้องขออย่างชัดเจน สำหรับชนิดที่มีอยู่แล้ว CTS จะจัดเตรียมการดำเนินการเพื่อดำเนินการบีบบังคับที่กว้างขึ้นโดยไม่มีการตรวจสอบรันไทม์และการบังคับให้แคบลงด้วยการตรวจสอบรันไทม์หรือการตัดทอนตามความหมายของการดำเนินการ

I.8.3.3 การหล่อ

เนื่องจากค่าสามารถมีได้มากกว่าหนึ่งประเภทการใช้ค่าจึงจำเป็นต้องระบุให้ชัดเจนว่าประเภทใดถูกใช้ เนื่องจากค่าถูกอ่านจากตำแหน่งที่พิมพ์ประเภทของค่าที่ใช้จึงเป็นประเภทของตำแหน่งที่อ่านค่า หากต้องใช้ประเภทอื่นค่าจะถูกเหวี่ยงเป็นประเภทอื่น การแคสต์มักเป็นการดำเนินการเวลาคอมไพล์ แต่ถ้าคอมไพลเลอร์ไม่สามารถทราบได้อย่างคงที่ว่าค่านั้นเป็นประเภทเป้าหมายการตรวจสอบรันไทม์จะเสร็จสิ้น ซึ่งแตกต่างจากการบีบบังคับนักแสดงจะไม่เปลี่ยนประเภทของวัตถุจริงและไม่เปลี่ยนการเป็นตัวแทน การหล่อจะรักษาเอกลักษณ์ของวัตถุ

ตัวอย่างเช่นอาจจำเป็นต้องใช้การตรวจสอบรันไทม์เมื่อแคสต์ค่าที่อ่านจากตำแหน่งที่พิมพ์ว่าถือค่าของอินเทอร์เฟซเฉพาะ เนื่องจากอินเทอร์เฟซเป็นคำอธิบายค่าที่ไม่สมบูรณ์การแคสต์ค่านั้นให้เป็นอินเทอร์เฟซประเภทอื่นจึงมักส่งผลให้มีการตรวจสอบรันไทม์


1

อ้างอิงจาก Wikipedia

ในวิทยาการคอมพิวเตอร์การแปลงประเภทการคัดเลือกประเภทการบังคับประเภทและการเล่นกลเป็นวิธีต่างๆในการเปลี่ยนนิพจน์จากประเภทข้อมูลหนึ่งไปยังอีกประเภทหนึ่ง

ความแตกต่างระหว่างการหล่อแบบและการบังคับประเภทมีดังนี้:

           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:

ในทางกลับกันการบีบบังคับแสดงถึงการสร้างวัตถุใหม่ในหน่วยความจำของประเภทใหม่จากนั้นประเภทดั้งเดิมจะถูกคัดลอกไปยังวัตถุใหม่โดยทิ้งวัตถุทั้งสองไว้ในหน่วยความจำ (จนกว่าผู้รวบรวมขยะจะนำออกไปหรือทั้งสองอย่าง) .

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.