Tại sao các phương thức giao diện C # không được khai báo là trừu tượng hoặc ảo?


108

Các phương thức C # trong giao diện được khai báo mà không sử dụng virtualtừ khóa và được ghi đè trong lớp dẫn xuất mà không sử dụng overridetừ khóa.

Có một lý do cho điều này? Tôi giả định rằng đó chỉ là một sự thuận tiện về ngôn ngữ và rõ ràng CLR biết cách xử lý điều này theo các điều khoản (các phương pháp không phải là ảo theo mặc định), nhưng có lý do kỹ thuật nào khác không?

Đây là IL mà một lớp dẫn xuất tạo ra:

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

Lưu ý rằng phương thức được khai báo virtual finaltrong IL.

Câu trả lời:


145

Đối với giao diện, việc bổ sung abstracthoặc thậm chí các publictừ khóa sẽ là thừa, vì vậy bạn bỏ qua chúng:

interface MyInterface {
  void Method();
}

Trong CIL, phương thức được đánh dấu virtualabstract.

(Lưu ý rằng Java cho phép khai báo các thành viên giao diện public abstract).

Đối với lớp triển khai, có một số tùy chọn:

Không thể ghi đè : Trong C #, lớp không khai báo phương thức là virtual. Điều đó có nghĩa là nó không thể bị ghi đè trong một lớp dẫn xuất (chỉ ẩn). Trong CIL, phương thức vẫn là ảo (nhưng được niêm phong) vì nó phải hỗ trợ tính đa hình liên quan đến kiểu giao diện.

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

Có thể ghi đè : Cả trong C # và trong CIL, phương thức này là virtual. Nó tham gia vào công văn đa hình và nó có thể bị ghi đè.

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

Rõ ràng : Đây là cách để một lớp triển khai một giao diện nhưng không cung cấp các phương thức giao diện trong giao diện chung của chính lớp đó. Trong CIL, phương thức sẽ là private(!) Nhưng nó vẫn có thể được gọi từ bên ngoài lớp từ một tham chiếu đến kiểu giao diện tương ứng. Các triển khai rõ ràng cũng không thể ghi đè. Điều này có thể thực hiện được vì có một chỉ thị CIL ( .override) sẽ liên kết phương thức private với phương thức giao diện tương ứng mà nó đang triển khai.

[C #]

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

[CIL]

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

Trong VB.NET, bạn thậm chí có thể đặt bí danh cho tên phương thức giao diện trong lớp thực thi.

[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
}

Bây giờ, hãy xem xét trường hợp kỳ lạ này:

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

Nếu BaseDerivedđược khai báo trong cùng một assembly, trình biên dịch sẽ tạo Base::Methodảo và được niêm phong (trong CIL), mặc dùBase không triển khai giao diện.

Nếu BaseDerivednằm trong các assembly khác nhau, khi biên dịch Derivedassembly, trình biên dịch sẽ không thay đổi assembly khác, vì vậy nó sẽ giới thiệu một thành viên trong Derivedđó sẽ là một triển khai rõ ràng cho MyInterface::Methodđiều đó sẽ chỉ ủy quyền cuộc gọi đến Base::Method.

Vì vậy, bạn thấy, mọi triển khai phương thức giao diện phải hỗ trợ hành vi đa hình và do đó phải được đánh dấu ảo trên CIL, ngay cả khi trình biên dịch phải trải qua vòng lặp để thực hiện điều đó.


73

Trích dẫn Jeffrey Ritcher từ CLR qua CSharp 3rd Edition tại đây

CLR yêu cầu các phương thức giao diện phải được đánh dấu là ảo. Nếu bạn không đánh dấu rõ ràng phương thức là ảo trong mã nguồn của mình, trình biên dịch sẽ đánh dấu phương thức là ảo và được niêm phong; điều này ngăn một lớp dẫn xuất ghi đè phương thức giao diện. Nếu bạn đánh dấu rõ ràng phương thức là ảo, trình biên dịch sẽ đánh dấu phương thức là ảo (và để nó không được niêm phong); điều này cho phép một lớp dẫn xuất ghi đè phương thức giao diện. Nếu một phương thức giao diện được niêm phong, một lớp dẫn xuất không thể ghi đè phương thức. Tuy nhiên, một lớp dẫn xuất có thể kế thừa lại cùng một giao diện và có thể cung cấp triển khai riêng cho các phương thức của giao diện.


25
Trích dẫn không nói lý do tại sao việc triển khai phương thức giao diện được yêu cầu được đánh dấu là ảo. Đó là bởi vì nó đa hình liên quan đến kiểu giao diện, vì vậy nó cần một vị trí trên bảng ảo để cho phép điều phối phương thức ảo.
Jordão

1
Tôi không được phép đánh dấu rõ ràng phương thức giao diện là ảo và gặp lỗi "lỗi CS0106: Công cụ sửa đổi 'ảo' không hợp lệ cho mục này". Đã thử nghiệm bằng v2.0.50727 (phiên bản đầu tiên trên PC của tôi).
ccppjava

3
@ccppjava Từ nhận xét của Jorado bên dưới, bạn đánh dấu thành viên lớp đang triển khai giao diện ảo để cho phép các lớp con ghi đè lớp.
Christopher Stevenson

13

Có, các phương pháp triển khai giao diện là ảo khi có liên quan đến thời gian chạy. Nó là một chi tiết thực hiện, nó làm cho các giao diện hoạt động. Các phương thức ảo nhận được các vị trí trong bảng v của lớp, mỗi vị trí có một con trỏ đến một trong các phương thức ảo. Truyền một đối tượng đến một kiểu giao diện tạo ra một con trỏ đến phần của bảng mà thực hiện các phương thức giao diện. Mã máy khách sử dụng tham chiếu giao diện bây giờ nhìn thấy con trỏ phương thức giao diện đầu tiên ở độ lệch 0 từ con trỏ giao diện, etcetera.

Điều tôi đánh giá thấp trong câu trả lời ban đầu là tầm quan trọng của thuộc tính cuối cùng . Nó ngăn một lớp dẫn xuất ghi đè phương thức ảo. Một lớp dẫn xuất phải thực hiện lại giao diện, các phương thức thực thi che khuất các phương thức của lớp cơ sở. Điều đó đủ để thực hiện hợp đồng ngôn ngữ C # nói rằng phương pháp thực hiện không ảo.

Nếu bạn khai báo phương thức Dispose () trong lớp Example là ảo, bạn sẽ thấy thuộc tính cuối cùng bị xóa. Bây giờ cho phép một lớp dẫn xuất ghi đè nó.


4

Trong hầu hết các môi trường mã đã biên dịch khác, các giao diện được triển khai dưới dạng vtables - một danh sách các con trỏ đến các thân phương thức. Thông thường, một lớp thực thi nhiều giao diện sẽ có một nơi nào đó trong siêu dữ liệu do trình biên dịch bên trong tạo ra một danh sách các vtables giao diện, một vtable trên mỗi giao diện (để thứ tự phương thức được giữ nguyên). Đây là cách các giao diện COM thường được triển khai.

Tuy nhiên, trong .NET, các giao diện không được triển khai dưới dạng các vtables riêng biệt cho mỗi lớp. Các phương thức giao diện được lập chỉ mục thông qua một bảng phương pháp giao diện toàn cục mà tất cả các giao diện đều là một phần của nó. Do đó, không cần thiết phải khai báo một phương thức ảo để phương thức đó thực hiện một phương thức giao diện - bảng phương thức giao diện toàn cục chỉ có thể trỏ trực tiếp đến địa chỉ mã của phương thức của lớp.

Việc khai báo một phương thức ảo để triển khai một giao diện cũng không cần thiết trong các ngôn ngữ khác, ngay cả trong các nền tảng không phải CLR. Ngôn ngữ Delphi trên Win32 là một ví dụ.


0

Chúng không ảo (về cách chúng tôi nghĩ về chúng, nếu không phải về triển khai cơ bản là (ảo được niêm phong) - tốt để đọc các câu trả lời khác ở đây và tự mình học hỏi điều gì đó :-)

Chúng không ghi đè bất cứ thứ gì - không có sự triển khai nào trong giao diện.

Tất cả những gì giao diện làm là cung cấp một "hợp đồng" mà lớp phải tuân theo - một mẫu, nếu bạn thích, để người gọi biết cách gọi đối tượng ngay cả khi họ chưa bao giờ nhìn thấy lớp cụ thể đó trước đây.

Sau đó tùy thuộc vào lớp để triển khai phương thức giao diện theo ý muốn, trong giới hạn của hợp đồng - ảo hoặc "không ảo" (ảo được niêm phong khi nó xuất hiện).


mọi người trong chủ đề này đều biết giao diện dùng để làm gì. Câu hỏi cực kỳ cụ thể - IL được tạo ảo đối với phương thức giao diện và không ảo đối với phương thức không giao diện.
Rex M

5
Vâng, thật dễ dàng để chỉ trích một câu trả lời sau khi câu hỏi đã được chỉnh sửa, phải không?
Jason Williams

0

Giao diện là một khái niệm trừu tượng hơn các lớp, khi bạn khai báo một lớp thực hiện một giao diện, bạn chỉ cần nói "lớp phải có các phương thức cụ thể này từ giao diện và không quan trọng khi tĩnh , ảo , không ảo , ghi đè , như miễn là nó có cùng ID và các thông số cùng loại ".

Các ngôn ngữ khác hỗ trợ giao diện như Object Pascal ("Delphi") và Objective-C (Mac) không yêu cầu các phương thức giao diện phải được đánh dấu là ảo và cũng không phải là ảo.

Nhưng, bạn có thể đúng, tôi nghĩ có thể là một ý tưởng hay khi có một thuộc tính "virtual" / "override" cụ thể trong các giao diện, trong trường hợp bạn muốn hạn chế các phương thức lớp triển khai một giao diện cụ thể. Tuy nhiên, điều đó cũng có nghĩa là phải có từ khóa "nonvirtual", "dontcareifvirtualornot", cho cả hai giao diện.

Tôi hiểu câu hỏi của bạn, vì tôi thấy điều gì đó tương tự trong Java, khi một phương thức lớp phải sử dụng "@virtual" hoặc "@override" để chắc chắn rằng một phương thức được dự định là ảo.


@override không thực sự thay đổi hành vi của mã hoặc thay đổi mã byte kết quả. Những gì nó làm là báo hiệu cho trình biên dịch rằng phương thức được trang trí như vậy là nhằm mục đích ghi đè, cho phép trình biên dịch thực hiện một số kiểm tra tỉnh táo. C # hoạt động khác nhau; overridelà một từ khóa hạng nhất trong chính ngôn ngữ.
Robert Harvey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.