เหตุใดวิธีการเชื่อมต่อ C # จึงไม่ประกาศว่าเป็นนามธรรมหรือเสมือน


108

วิธีการ C # ในอินเทอร์เฟซถูกประกาศโดยไม่ใช้virtualคีย์เวิร์ดและแทนที่ในคลาสที่ได้รับโดยไม่ต้องใช้overrideคีย์เวิร์ด

มีเหตุผลนี้หรือไม่? ฉันคิดว่ามันเป็นเพียงความสะดวกทางภาษาและเห็นได้ชัดว่า CLR รู้วิธีจัดการสิ่งนี้ภายใต้ฝาครอบ (วิธีการไม่ใช่เสมือนตามค่าเริ่มต้น) แต่มีเหตุผลทางเทคนิคอื่น ๆ หรือไม่?

นี่คือ IL ที่คลาสที่ได้รับสร้างขึ้น:

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

สังเกตว่าเมธอดถูกประกาศvirtual finalใน IL

คำตอบ:


145

สำหรับอินเทอร์เฟซการเพิ่มabstractหรือแม้แต่publicคำหลักจะซ้ำซ้อนดังนั้นคุณจึงละเว้น:

interface MyInterface {
  void Method();
}

ใน CIL วิธีการทำเครื่องหมายและvirtualabstract

(โปรดทราบว่า Java อนุญาตให้ประกาศสมาชิกอินเตอร์เฟสได้public abstract)

สำหรับคลาสการใช้งานมีตัวเลือกบางอย่าง:

Non-overridable : ใน C # คลาสไม่ได้ประกาศเมธอดเป็นvirtual. นั่นหมายความว่าไม่สามารถแทนที่ในคลาสที่ได้รับ (ซ่อนไว้เท่านั้น) ใน CIL เมธอดยังคงเป็นเสมือน (แต่ปิดผนึก) เนื่องจากต้องรองรับความหลากหลายเกี่ยวกับประเภทอินเทอร์เฟซ

class MyClass : MyInterface {
  public void Method() {}
}

Overridable : ทั้งสองใน C # และ CIL virtualวิธีคือ มีส่วนร่วมในการจัดส่งหลายรูปแบบและสามารถลบล้างได้

class MyClass : MyInterface {
  public virtual void Method() {}
}

Explicit : นี่เป็นวิธีสำหรับคลาสในการใช้อินเทอร์เฟซ แต่ไม่ได้จัดเตรียมเมธอดอินเตอร์เฟสในอินเทอร์เฟซสาธารณะของคลาสเอง ใน CIL เมธอดจะเป็นprivate(!) แต่จะยังสามารถเรียกใช้ได้จากภายนอกคลาสจากการอ้างอิงถึงประเภทอินเทอร์เฟซที่เกี่ยวข้อง การใช้งานที่ชัดเจนยังไม่สามารถเขียนทับได้ สิ่งนี้เป็นไปได้เนื่องจากมีคำสั่ง CIL ( .override) ที่จะเชื่อมโยงเมธอดส่วนตัวกับวิธีอินเทอร์เฟซที่เกี่ยวข้องซึ่งกำลังใช้งานอยู่

[ค#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

ใน VB.NET คุณสามารถตั้งชื่อเมธอดของอินเทอร์เฟซในคลาสการใช้

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

ลองพิจารณากรณีแปลก ๆ นี้:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

หากBaseและDerivedถูกประกาศในแอสเซมบลีเดียวกันคอมไพเลอร์จะสร้างBase::Methodเสมือนและปิดผนึก (ใน CIL) แม้ว่าจะBaseไม่ได้ใช้อินเทอร์เฟซ

ถ้าBaseและDerivedอยู่ในคริสตจักรที่แตกต่างกันเมื่อรวบรวมDerivedประกอบคอมไพเลอร์จะไม่เปลี่ยนการชุมนุมอื่น ๆ ดังนั้นจึงจะแนะนำสมาชิกDerivedที่จะมีการดำเนินการที่ชัดเจนสำหรับที่เพิ่งจะมอบหมายการเรียกร้องให้MyInterface::MethodBase::Method

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


73

อ้างถึง Jeffrey Ritcher จาก CLR ผ่าน CSharp 3rd Edition ที่นี่

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


25
ใบเสนอราคาไม่ได้บอกว่าทำไมต้องใช้วิธีการอินเทอร์เฟซเพื่อทำเครื่องหมายเสมือน เป็นเพราะมันมีความหลากหลายเกี่ยวกับประเภทอินเทอร์เฟซดังนั้นจึงต้องมีสล็อตบนตารางเสมือนเพื่อให้สามารถจัดส่งวิธีเสมือนได้
Jordão

1
ฉันไม่ได้รับอนุญาตให้ทำเครื่องหมายวิธีการอินเทอร์เฟซเป็นเสมือนอย่างชัดเจนและได้รับข้อผิดพลาด "ข้อผิดพลาด CS0106: ตัวปรับแต่ง" เสมือน "ไม่ถูกต้องสำหรับรายการนี้" ทดสอบโดยใช้ v2.0.50727 (เวอร์ชันที่เก่าที่สุดในพีซีของฉัน)
ccppjava

3
@ccppjava จากความคิดเห็นของ Jorado ด้านล่างคุณทำเครื่องหมายสมาชิกชั้นเรียนที่ใช้อินเทอร์เฟซเสมือนเพื่ออนุญาตให้คลาสย่อยแทนที่คลาสได้
Christopher Stevenson

13

ใช่วิธีการใช้งานอินเทอร์เฟซเป็นเสมือนจริงเท่าที่รันไทม์เกี่ยวข้อง มันเป็นรายละเอียดการใช้งานทำให้อินเทอร์เฟซทำงานได้ เมธอดเสมือนได้รับสล็อตใน v-table ของคลาสแต่ละสล็อตมีตัวชี้ไปยังหนึ่งในเมธอดเสมือน การแคสต์อ็อบเจ็กต์ไปยังประเภทอินเทอร์เฟซจะสร้างตัวชี้ไปยังส่วนของตารางที่ใช้เมธอดอินเทอร์เฟซ ตอนนี้รหัสไคลเอ็นต์ที่ใช้การอ้างอิงอินเทอร์เฟซจะเห็นตัวชี้วิธีการอินเทอร์เฟซตัวแรกที่ออฟเซ็ต 0 จากตัวชี้อินเทอร์เฟซ ฯลฯ

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

หากคุณประกาศเมธอด Dispose () ในคลาสตัวอย่างเป็นเสมือนคุณจะเห็นแอ็ตทริบิวต์สุดท้ายถูกลบออก ตอนนี้อนุญาตให้คลาสที่ได้รับมาแทนที่มัน


4

ในสภาพแวดล้อมโค้ดที่คอมไพล์อื่น ๆ ส่วนใหญ่อินเทอร์เฟซจะถูกนำไปใช้เป็น vtables - รายการของตัวชี้ไปยังเนื้อหาของวิธีการ โดยทั่วไปแล้วคลาสที่ใช้หลายอินเทอร์เฟซจะมีบางส่วนในคอมไพเลอร์ภายในที่สร้างเมทาดาทารายการของอินเตอร์เฟส vtables หนึ่ง vtable ต่ออินเทอร์เฟซ (เพื่อให้ลำดับเมธอดถูกเก็บรักษาไว้) นี่คือวิธีการใช้งานอินเตอร์เฟส COM โดยทั่วไปเช่นกัน

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

การประกาศเมธอดเสมือนเพื่อใช้อินเทอร์เฟซไม่จำเป็นต้องใช้ในภาษาอื่นเช่นกันแม้ในแพลตฟอร์มที่ไม่ใช่ CLR ภาษาเดลฟีบน Win32 เป็นตัวอย่างหนึ่ง


0

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

พวกเขาไม่ได้ลบล้างสิ่งใดเลย - ไม่มีการใช้งานในอินเทอร์เฟซ

อินเทอร์เฟซทั้งหมดทำคือจัดหา "สัญญา" ที่คลาสต้องปฏิบัติตาม - รูปแบบหากคุณต้องการเพื่อให้ผู้โทรทราบวิธีเรียกวัตถุแม้ว่าพวกเขาจะไม่เคยเห็นคลาสนั้นมาก่อนก็ตาม

มันขึ้นอยู่กับคลาสแล้วที่จะใช้วิธีการอินเทอร์เฟซตามที่ต้องการภายในขอบเขตของสัญญา - เสมือนหรือ "ไม่ใช่เสมือน" (ปิดผนึกเสมือนตามที่ปรากฎ)


ทุกคนในเธรดนี้รู้ว่าอินเทอร์เฟซมีไว้เพื่ออะไร คำถามมีความเฉพาะเจาะจงมาก - IL ที่สร้างขึ้นเป็นเสมือนสำหรับเมธอดอินเทอร์เฟซและไม่ใช่เสมือนสำหรับวิธีที่ไม่ใช่อินเทอร์เฟซ
Rex M

5
ใช่มันง่ายมากที่จะวิจารณ์คำตอบหลังจากแก้ไขคำถามแล้วใช่ไหม
Jason Williams

0

อินเตอร์เฟซที่เป็นแนวคิดที่เป็นนามธรรมมากกว่าการเรียนเมื่อคุณประกาศคลาสที่ใช้อินเตอร์เฟซที่คุณเพียงแค่บอกว่า "ชั้นจะต้องมีวิธีการเหล่านี้โดยเฉพาะจากอินเตอร์เฟซและไม่ได้เรื่อง wheter คง , เสมือน , ไม่ใช่เสมือน , overridenเป็น ตราบใดที่มี ID เดียวกันและพารามิเตอร์ประเภทเดียวกัน "

ภาษาอื่น ๆ ที่รองรับอินเทอร์เฟซเช่น Object Pascal ("Delphi") และ Objective-C (Mac) ไม่จำเป็นต้องมีวิธีการเชื่อมต่อเพื่อทำเครื่องหมายว่าเสมือนและไม่ใช่เสมือน

แต่คุณคิดถูกฉันคิดว่าอาจเป็นความคิดที่ดีที่จะมีแอตทริบิวต์ "virtual" / "override" เฉพาะในอินเทอร์เฟซในกรณีที่คุณต้องการจำกัดเมธอดคลาสที่ใช้อินเทอร์เฟซเฉพาะ แต่นั่นหมายถึงการมีคีย์เวิร์ด "nonvirtual", "dontcareifvirtualornot" สำหรับทั้งสองอินเทอร์เฟซ

ฉันเข้าใจคำถามของคุณเพราะฉันเห็นบางอย่างที่คล้ายกันใน Java เมื่อเมธอดคลาสต้องใช้เครื่องหมาย "@virtual" หรือ "@override" เพื่อให้แน่ใจว่าเมธอดนั้นตั้งใจให้เป็นเสมือนจริง


@override ไม่ได้เปลี่ยนลักษณะการทำงานของโค้ดหรือแก้ไขโค้ดไบต์ที่เป็นผลลัพธ์ สิ่งที่ทำคือส่งสัญญาณให้คอมไพเลอร์ทราบว่าวิธีการตกแต่งดังกล่าวมีจุดมุ่งหมายเพื่อเป็นการลบล้างซึ่งทำให้คอมไพเลอร์สามารถตรวจสอบความมีสติได้ C # ทำงานแตกต่างกัน overrideเป็นคีย์เวิร์ดชั้นหนึ่งในภาษานั้น ๆ
Robert Harvey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.