Tại sao không phải C # Cho phép phương pháp tĩnh để thực hiện một giao diện?


447

Tại sao C # được thiết kế theo cách này?

Theo tôi hiểu, một giao diện chỉ mô tả hành vi và phục vụ mục đích mô tả nghĩa vụ theo hợp đồng đối với các lớp thực hiện giao diện mà hành vi nhất định được thực hiện.

Nếu các lớp muốn thực hiện hành vi đó theo một phương thức được chia sẻ, tại sao chúng không nên?

Đây là một ví dụ về những gì tôi có trong tâm trí:

// These items will be displayed in a list on the screen.
public interface IListItem {
  string ScreenName();
  ...
}

public class Animal: IListItem {
    // All animals will be called "Animal".
    public static string ScreenName() {
        return "Animal";
    }
....
}

public class Person: IListItem {

    private string name;

    // All persons will be called by their individual names.
    public string ScreenName() {
        return name;
    }

    ....

 }


1
Xem cách bạn có thể kết hợp một hành vi tĩnh với kế thừa hoặc triển khai giao diện: stackoverflow.com/a/13567309/880990
Olivier Jacot-Descombes

1
IListItem.ScreenName() => ScreenName()(sử dụng cú pháp C # 7) sẽ thực hiện phương thức giao diện một cách rõ ràng bằng cách gọi phương thức tĩnh. Tuy nhiên, mọi thứ trở nên tồi tệ khi bạn thêm tính kế thừa vào đó (bạn phải thực hiện lại giao diện)
Jeroen Mostert

2
Chỉ cần cho mọi người biết rằng sự chờ đợi đã kết thúc! C # 8.0 có các phương thức giao diện tĩnh: dotnetfiddle.net/Lrzy6y (mặc dù chúng hoạt động hơi khác so với cách OP muốn chúng hoạt động - bạn không phải thực hiện chúng)
Jamie Twells 21/11/19

Câu trả lời:


221

Giả sử bạn đang hỏi tại sao bạn không thể làm điều này:

public interface IFoo {
    void Bar();
}

public class Foo: IFoo {
    public static void Bar() {}
}

Điều này không có ý nghĩa với tôi, về mặt ngữ nghĩa. Các phương thức được chỉ định trên một giao diện sẽ ở đó để chỉ định hợp đồng tương tác với một đối tượng. Các phương thức tĩnh không cho phép bạn tương tác với một đối tượng - nếu bạn thấy mình ở vị trí mà việc triển khai của bạn có thể được thực hiện tĩnh, bạn có thể cần phải tự hỏi liệu phương thức đó có thực sự thuộc về giao diện không.


Để thực hiện ví dụ của bạn, tôi sẽ cung cấp cho Animal một thuộc tính const, vẫn sẽ cho phép nó được truy cập từ ngữ cảnh tĩnh và trả về giá trị đó trong quá trình thực hiện.

public class Animal: IListItem {
    /* Can be tough to come up with a different, yet meaningful name!
     * A different casing convention, like Java has, would help here.
     */
    public const string AnimalScreenName = "Animal";
    public string ScreenName(){ return AnimalScreenName; }
}

Đối với một tình huống phức tạp hơn, bạn luôn có thể khai báo một phương thức tĩnh khác và ủy quyền cho điều đó. Khi thử đưa ra một ví dụ, tôi không thể nghĩ ra bất kỳ lý do nào bạn sẽ làm một việc gì đó không tầm thường trong cả bối cảnh tĩnh và cá thể, vì vậy tôi sẽ dành cho bạn một blob FooBar, và coi đó là một dấu hiệu cho thấy nó có thể không phải là một ý tưởng tốt


6
Một ví dụ hoàn hảo! Nhưng tôi không chắc tôi hiểu lý do của bạn. Chắc chắn trình biên dịch có thể đã được thiết kế để xem xét các thành viên statuc không? Các trường hợp có một bảng địa chỉ để thực hiện các phương thức này không? Không thể bao gồm các phương thức tĩnh trong bảng này?
Kramii

12
Có một trường hợp điều này có thể hữu ích. Chẳng hạn, tôi muốn tất cả những người triển khai thực hiện phương thức GetInstance có đối số XEuity. Tôi không thể định nghĩa đó là một phương thức tĩnh trong giao diện, cũng không yêu cầu chữ ký của hàm tạo từ giao diện.
oleks

25
Rất nhiều người đã cân nhắc từ cả hai phía: từ "Điều này vô nghĩa" đến "Đó là một lỗi, tôi ước bạn có thể." (Tôi nghĩ rằng có các trường hợp sử dụng hợp lệ cho nó, đó là cách tôi kết thúc ở đây.) Như một cách giải quyết, phương thức cá thể có thể chỉ cần ủy thác cho một phương thức tĩnh.
Harpo

5
Bạn cũng có thể đơn giản thực hiện phần này như một phương thức mở rộng trên giao diện cơ sở, như trong: public static object MethodName(this IBaseClass base)trong một lớp tĩnh. Tuy nhiên, nhược điểm là không giống như kế thừa giao diện - điều này không bắt buộc / cho phép các thừa kế riêng lẻ ghi đè tốt phương pháp.
Troy Alford

8
Nó sẽ có rất nhiều ý nghĩa với thuốc generic. Ví dụ: void Something <T> () trong đó T: ISomeInterface {new T (). DoS Something1 (); T.DoSthing2 (); }
Francisco Ryan Tolmasky I

173

Lý do kỹ thuật (đơn giản hóa) của tôi là các phương thức tĩnh không có trong vtable và trang web cuộc gọi được chọn tại thời điểm biên dịch. Đó là cùng một lý do bạn không thể ghi đè hoặc thành viên tĩnh ảo. Để biết thêm chi tiết, bạn cần một loại CS hoặc trình biên dịch wonk - trong đó tôi cũng không.

Vì lý do chính trị, tôi sẽ trích dẫn Eric Lippert (một người biên dịch wonk, và có bằng Cử nhân Toán học, Khoa học Máy tính và Toán học Ứng dụng từ Đại học Waterloo (nguồn: LinkedIn ):

... Nguyên tắc thiết kế cốt lõi của các phương thức tĩnh, nguyên tắc cung cấp cho chúng tên của chúng ... [là] ... nó luôn có thể được xác định chính xác, tại thời điểm biên dịch, phương thức nào sẽ được gọi. Đó là, phương pháp có thể được giải quyết chỉ bằng cách phân tích tĩnh mã.

Lưu ý rằng Lippert không chừa chỗ cho phương thức được gọi là:

Đó là, một phương thức được liên kết với một loại (như tĩnh), không lấy một đối số điều này không thể vô hiệu hóa đối số này (không giống như một thể hiện hoặc ảo), nhưng một phương thức mà phương thức được gọi sẽ phụ thuộc vào loại T được xây dựng ( Không giống như một tĩnh, phải được xác định tại thời gian biên dịch).

nhưng vẫn chưa được thuyết phục về tính hữu dụng của nó.


5
Tuyệt vời, đây là câu trả lời mà tôi muốn viết - tôi chỉ không biết chi tiết thực hiện.
Chris Marasti-Georg

5
Câu trả lời chính xác. Và tôi muốn "phương pháp loại" này! Sẽ hữu ích trong rất nhiều trường hợp (nghĩ về siêu dữ liệu cho một loại / lớp).
Philip Daubmeier

23
Đây là câu trả lời chính xác. Bạn truyền một giao diện cho ai đó, họ cần biết cách gọi một phương thức. Một giao diện chỉ là một bảng phương thức ảo . Lớp tĩnh của bạn không có điều đó. Người gọi sẽ không biết cách gọi một phương thức. (Trước khi tôi đọc câu trả lời này, tôi đã nghĩ rằng C # chỉ mang tính mô phạm. Bây giờ tôi nhận ra đó là một giới hạn kỹ thuật, được áp đặt bởi giao diện là gì ). Những người khác sẽ nói chuyện với bạn về cách nó là một thiết kế tồi. Đây không phải là một thiết kế tồi - đó là một hạn chế kỹ thuật.
Ian Boyd

2
+1 cho việc thực sự trả lời câu hỏi và không mang tính mô phạm và lén lút. Giống như Ian Boyd đã nói: "Đây không phải là một thiết kế tồi - đó là một hạn chế về kỹ thuật."
Joshua Pech

3
Hoàn toàn có thể tạo một đối tượng cho một lớp tĩnh với vtable được liên kết. Nhìn vào cách Scala xử lý objects và cách chúng được phép thực hiện giao diện.
Sebastian Graf

97

Hầu hết các câu trả lời ở đây dường như bỏ lỡ toàn bộ điểm. Đa hình có thể được sử dụng không chỉ giữa các trường hợp, mà còn giữa các loại. Điều này thường là cần thiết, khi chúng ta sử dụng thuốc generic.

Giả sử chúng ta có tham số kiểu trong phương thức chung và chúng ta cần thực hiện một số thao tác với nó. Chúng tôi không muốn khởi tạo, bởi vì chúng tôi không biết về các nhà xây dựng.

Ví dụ:

Repository GetRepository<T>()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository<Customer>()

Thật không may, tôi chỉ có thể đến với các lựa chọn thay thế "xấu xí":

  • Sử dụng sự phản chiếu Xấu xí và đánh bại ý tưởng về giao diện và đa hình.

  • Tạo lớp nhà máy hoàn toàn riêng biệt

    Điều này có thể làm tăng đáng kể sự phức tạp của mã. Ví dụ, nếu chúng ta đang cố gắng mô hình hóa các đối tượng miền, mỗi đối tượng sẽ cần một lớp kho lưu trữ khác.

  • Khởi tạo và sau đó gọi phương thức giao diện mong muốn

    Điều này có thể khó thực hiện ngay cả khi chúng ta kiểm soát nguồn cho các lớp, được sử dụng làm tham số chung. Lý do là, ví dụ, chúng ta có thể cần các thể hiện chỉ ở trạng thái "được kết nối với DB" nổi tiếng.

Thí dụ:

public class Customer 
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  void SomeOtherMethod() 
  { 
    //do work...
  }
}

Để sử dụng tính năng khởi tạo để giải quyết vấn đề giao diện tĩnh, chúng ta cần thực hiện các thao tác sau:

public class Customer: IDoSomeStaticMath
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  //dummy instance
  public Customer() { IsDummy = true; }

  int DoSomeStaticMath(int a) { }

  void SomeOtherMethod() 
  { 
    if(!IsDummy) 
    {
      //do work...
    }
  }
}

Điều này rõ ràng là xấu xí và cũng không cần thiết làm phức tạp mã cho tất cả các phương pháp khác. Rõ ràng, không phải là một giải pháp thanh lịch!


35
+1 cho "Hầu hết các câu trả lời ở đây dường như bỏ lỡ toàn bộ vấn đề."; thật đáng kinh ngạc khi có vẻ như hầu hết tất cả các câu trả lời đều né tránh cốt lõi của câu hỏi để đi sâu vào những câu nói khó hiểu nhất ...

5
@Chris Đây là một ví dụ cụ thể khiến tôi lại gặp phải hạn chế này. Tôi muốn thêm giao diện IResitable vào các lớp để cho biết rằng họ lưu trữ dữ liệu nhất định trong các biến tĩnh có thể được đặt lại bởi quản trị viên trang web (ví dụ: danh sách danh mục đơn hàng, tập hợp tỷ lệ vat, danh sách các danh mục được truy xuất từ ​​API bên ngoài) để giảm DB và các lần truy cập API bên ngoài, điều này rõ ràng sẽ sử dụng thiết lập lại phương thức tĩnh. Điều này cho phép tôi tự động phát hiện các lớp nào có thể được thiết lập lại. Tôi vẫn có thể làm điều này, nhưng phương thức không được thi hành hoặc tự động thêm vào IDE và dựa vào hy vọng.
mattmanser

6
@Chris Tôi không đồng ý, quá mức cần thiết. Đó luôn là một dấu hiệu của một lỗ hổng ngôn ngữ khi nhiều kiến ​​trúc là giải pháp 'tốt nhất'. Hãy nhớ tất cả các mẫu mà không ai nói về nữa vì C # có các phương pháp chung và ẩn danh?
mattmanser

3
Bạn không thể sử dụng "where T: IQueryable, T: IDoSomeStaticMath" hoặc tương tự?
Roger Willcocks

2
@ ChrisMarasti-Georg: Một cách hơi bẩn thỉu nhưng thú vị xung quanh nó là công trình nhỏ này: public abstract class DBObject<T> where T : DBObject<T>, new()và sau đó làm cho tất cả các lớp DB được kế thừa như DBObject<T>. Sau đó, bạn có thể biến khóa truy xuất thành một hàm tĩnh với kiểu trả về T trong siêu lớp trừu tượng, làm cho hàm đó tạo một đối tượng mới của T và sau đó gọi một bảo vệ String GetRetrieveByKeyQuery()(được định nghĩa là trừu tượng trên siêu lớp) trên đối tượng đó để lấy truy vấn thực tế để thực hiện. Mặc dù điều này có thể bị lạc đề
Nyerguds

20

Tôi biết đó là một câu hỏi cũ, nhưng nó thú vị. Ví dụ không phải là tốt nhất. Tôi nghĩ sẽ rõ ràng hơn nhiều nếu bạn đưa ra trường hợp sử dụng:

chuỗi DoS Something <T> () trong đó T: ISomeFunction
{
  if (T.someFunction ())
    ...
}

Chỉ có thể có các phương thức tĩnh thực hiện một giao diện sẽ không đạt được những gì bạn muốn; những gì cần thiết sẽ có các thành viên tĩnh như một phần của giao diện. Tôi chắc chắn có thể tưởng tượng nhiều trường hợp sử dụng cho điều đó, đặc biệt là khi có thể tạo ra mọi thứ. Hai cách tiếp cận tôi có thể đưa ra có thể hữu ích:

  1. Tạo một lớp chung tĩnh có tham số loại sẽ là loại bạn sẽ chuyển đến DoS Something ở trên. Mỗi biến thể của lớp này sẽ có một hoặc nhiều thành viên tĩnh chứa các thứ liên quan đến loại đó. Thông tin này có thể được cung cấp bằng cách yêu cầu mỗi lớp quan tâm gọi một thói quen "đăng ký thông tin" hoặc bằng cách sử dụng Reflection để lấy thông tin khi hàm tạo tĩnh của biến thể lớp được chạy. Tôi tin rằng cách tiếp cận thứ hai được sử dụng bởi những thứ như So sánh <T> .Default ().
  2. Đối với mỗi lớp T quan tâm, hãy xác định một lớp hoặc cấu trúc thực hiện IGetWhthingClassInfo <T> và thỏa mãn một ràng buộc "mới". Lớp sẽ không thực sự chứa bất kỳ trường nào, nhưng sẽ có một thuộc tính tĩnh trả về trường tĩnh với thông tin loại. Truyền loại của lớp hoặc cấu trúc đó cho thường trình chung trong câu hỏi, sẽ có thể tạo một thể hiện và sử dụng nó để lấy thông tin về lớp khác. Nếu bạn sử dụng một lớp cho mục đích này, có lẽ bạn nên định nghĩa một lớp chung tĩnh như được chỉ ra ở trên, để tránh phải xây dựng một thể hiện đối tượng mô tả mới mỗi lần. Nếu bạn sử dụng một cấu trúc, chi phí khởi tạo sẽ là không, nhưng mỗi loại cấu trúc khác nhau sẽ yêu cầu một sự mở rộng khác nhau của thói quen DoSthing.

Không có cách tiếp cận nào trong số này là thực sự hấp dẫn. Mặt khác, tôi hy vọng rằng nếu các cơ chế tồn tại trong CLR cung cấp loại chức năng này một cách sạch sẽ, thì .net sẽ cho phép một người chỉ định các ràng buộc "mới" được tham số hóa (vì biết rằng một lớp có hàm tạo có chữ ký cụ thể không để có thể so sánh khó khăn để biết nếu nó có một phương thức tĩnh với một chữ ký cụ thể).


16

Cận thị, tôi đoán.

Khi được thiết kế ban đầu, các giao diện chỉ được sử dụng với các thể hiện của lớp

IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();

Chỉ với việc giới thiệu các giao diện là các ràng buộc cho các tổng quát đã thêm một phương thức tĩnh vào một giao diện có một cách sử dụng thực tế.

(trả lời bình luận :) Tôi tin rằng việc thay đổi nó bây giờ sẽ yêu cầu thay đổi CLR, điều này sẽ dẫn đến sự không tương thích với các hội đồng hiện có.


Đó là trong bối cảnh chung chung mà lần đầu tiên tôi gặp phải vấn đề, nhưng tôi tự hỏi liệu bao gồm các phương thức tĩnh trong giao diện có thể hữu ích trong các bối cảnh khác không? Có một lý do tại sao mọi thứ không thể được thay đổi?
Kramii

Tôi cũng gặp phải điều này khi thực hiện một lớp chung yêu cầu loại tham số để tự tạo với một số tham số. Vì mới () không thể lấy bất kỳ. Bạn đã tìm ra cách để làm điều này chưa, Kramii?
Tom

1
@Kramii: Hợp đồng cho các API tĩnh. Tôi không muốn một đối tượng, chỉ cần một sự đảm bảo về một chữ ký cụ thể, vd. IMatrixMultiplier hoặc ICustomSerializer. Funcs / Action / Delegates khi các thành viên trong lớp thực hiện mánh khóe, nhưng IMO điều này đôi khi có vẻ như quá mức cần thiết và có thể gây nhầm lẫn cho những người chưa có kinh nghiệm khi cố gắng mở rộng API.
David Cuccia

14

Các giao diện xác định hành vi của một đối tượng.

Các phương thức tĩnh không chỉ định một hành vi của một đối tượng, nhưng hành vi đó ảnh hưởng đến một đối tượng theo một cách nào đó.


71
Xin lỗi .. tôi không chắc điều đó đúng! Một giao diện không chỉ định hành vi. Một giao diện xác định một tập hợp các hoạt động được đặt tên. Hai lớp có thể thực hiện phương thức của một giao diện để hành xử theo những cách hoàn toàn khác nhau. Vì vậy, một giao diện hoàn toàn không xác định hành vi. Các lớp thực hiện nó làm.
Scott Langham

1
Hy vọng bạn không nghĩ tôi kén chọn .. nhưng tôi nghĩ đó là một sự khác biệt quan trọng đối với bất kỳ ai học OO để hiểu.
Scott Langham

4
Một giao diện được cho là chỉ định một hợp đồng bao gồm hành vi và trình bày. Đó là lý do tại sao việc thay đổi hành vi của một cuộc gọi giao diện là không nên vì cả hai đều phải sửa. Nếu bạn có một giao diện trong đó cuộc gọi hoạt động khác đi (ví dụ IList. Đã xóa) thì sẽ không đúng.
Jeff Yates

14
Vâng, vâng, bạn sẽ bị vênh trong đầu để xác định một phương pháp để hành xử không nhất quán với tên của nó. Nếu bạn đã có IAlertService.GetAssistance (), hành vi của nó có thể là nháy đèn, phát ra âm thanh báo động hoặc chọc vào mắt bằng gậy.
Scott Langham

1
Một triển khai cũng có thể ghi vào một tệp nhật ký. Hành vi này không được chỉ định trong giao diện. Nhưng, có lẽ bạn đã đúng. Hành vi để 'nhận trợ giúp' thực sự cần được tôn trọng.
Scott Langham

14

Trong phạm vi các giao diện đại diện cho "hợp đồng", có vẻ yên tĩnh hợp lý cho các lớp tĩnh để thực hiện giao diện.

Các lý lẽ trên dường như bỏ lỡ điểm này về hợp đồng.


2
Tôi hoàn toàn đồng ý với câu trả lời đơn giản nhưng hiệu quả này. Điều thú vị trong "giao diện tĩnh" là nó sẽ đại diện cho một hợp đồng. Có lẽ nó không nên được gọi là "giao diện tĩnh" nhưng chúng ta vẫn bỏ lỡ một cấu trúc. Ví dụ: kiểm tra tài liệu chính thức của .NET về giao diện ICustomMarshaler. Nó yêu cầu lớp thực hiện nó để "thêm một phương thức tĩnh có tên GetInstance chấp nhận Chuỗi làm tham số và có kiểu trả về ICustomMarshaler". Điều đó thực sự trông giống như một định nghĩa "giao diện tĩnh" trong tiếng Anh đơn giản trong khi tôi thích nó hơn trong C # ...
Simon Mourier

@SimonMourier Tài liệu đó có thể đã được viết rõ ràng hơn, nhưng bạn đang hiểu sai nó. Đây không phải là giao diện ICustomMarshaler yêu cầu phương thức tĩnh GetInstance. Đây là thuộc tính mã [MarshalAs] yêu cầu điều này. Họ đang sử dụng mẫu nhà máy để cho phép thuộc tính lấy một thể hiện của nguyên soái đính kèm. Thật không may, họ hoàn toàn quên bao gồm ghi lại yêu cầu GetInstance trên trang tài liệu MarshalAs (nó chỉ hiển thị các ví dụ sử dụng triển khai đầm lầy tích hợp).
Scott Gartner

@ScottGartner - Đừng hiểu ý bạn. msdn.microsoft.com/en-us/l Library /. Nói rõ: "Ngoài việc triển khai giao diện ICustomMarshaler, các soái ca tùy chỉnh phải triển khai một phương thức tĩnh có tên GetInstance chấp nhận Chuỗi làm tham số và có kiểu trả về ICustomMarshaler. Phương thức tĩnh được gọi bởi lớp xen kẽ COM của ngôn ngữ chung để khởi tạo một thể hiện của trình sắp xếp tùy chỉnh. ". Đây chắc chắn là một định nghĩa hợp đồng tĩnh.
Simon Mourier

9

Vì mục đích của giao diện là cho phép đa hình, có thể vượt qua một thể hiện của bất kỳ số lượng các lớp được xác định nào đã được xác định để thực hiện giao diện đã xác định ... đảm bảo rằng trong cuộc gọi đa hình của bạn, mã sẽ có thể tìm thấy phương thức bạn đang gọi. thật vô nghĩa khi cho phép một phương thức tĩnh thực hiện giao diện,

Làm thế nào bạn sẽ gọi nó ??


public interface MyInterface { void MyMethod(); }
public class MyClass: MyInterface
{
    public static void MyMethod() { //Do Something; }
}

 // inside of some other class ...  
 // How would you call the method on the interface ???
    MyClass.MyMethod();  // this calls the method normally 
                         // not through the interface...

    // This next fails you can't cast a classname to a different type... 
    // Only instances can be Cast to a different type...
    MyInterface myItf = MyClass as MyInterface;  

1
Các ngôn ngữ khác (ví dụ Java) cho phép các phương thức tĩnh được gọi từ các thể hiện đối tượng, mặc dù bạn sẽ nhận được một cảnh báo rằng bạn nên gọi chúng từ ngữ cảnh tĩnh.
Chris Marasti-Georg

Cho phép một phương thức tĩnh được gọi từ một cá thể cũng được cho phép trong .Net. Đó là một điều khác biệt. Nếu một phương thức tĩnh thực hiện một giao diện, bạn có thể gọi nó KHÔNG CÓ một thể hiện. ĐÓ là những gì không có ý nghĩa. Hãy nhớ rằng bạn không thể đặt triển khai trong một giao diện, nó phải ở trong lớp. Vì vậy, nếu năm lớp khác nhau được định nghĩa để thực hiện giao diện đó và mỗi lớp có một cách thực hiện khác nhau của phương thức tĩnh này, trình biên dịch sẽ sử dụng?
Charles Bretana

1
@CharlesBretana trong trường hợp generic, một loại cho loại được truyền vào. Tôi thấy rất hữu ích trong việc có giao diện cho các phương thức tĩnh (hoặc nếu bạn muốn, hãy gọi chúng là "giao diện tĩnh" và cho phép một lớp xác định cả hai giao diện tĩnh và giao diện thể hiện). Vì vậy, nếu tôi có Whatever<T>() where T:IMyStaticInterfacetôi có thể gọi Whatever<MyClass>()và có T.MyStaticMethod()bên trong Whatever<T>()triển khai mà không cần một ví dụ. Phương thức để gọi sẽ được xác định trong thời gian chạy. Bạn có thể làm điều này thông qua sự phản ánh, nhưng không có "hợp đồng" nào bị ép buộc.
Jcl 3/03/2015

4

Về các phương thức tĩnh được sử dụng trong các ngữ cảnh không chung chung, tôi đồng ý rằng việc cho phép chúng trong các giao diện không có ý nghĩa gì, vì dù sao bạn cũng không thể gọi chúng nếu bạn có tham chiếu đến giao diện. Tuy nhiên, có một lỗ hổng cơ bản trong thiết kế ngôn ngữ được tạo ra bằng cách sử dụng các giao diện KHÔNG trong bối cảnh đa hình, nhưng trong một bối cảnh chung. Trong trường hợp này, giao diện hoàn toàn không phải là một giao diện mà là một ràng buộc. Vì C # không có khái niệm về một ràng buộc bên ngoài giao diện nên nó thiếu chức năng đáng kể. Trường hợp tại điểm:

T SumElements<T>(T initVal, T[] values)
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

Ở đây không có tính đa hình, chung sử dụng loại thực tế của đối tượng và gọi toán tử + =, nhưng điều này không thành công vì không thể chắc chắn rằng toán tử đó tồn tại. Giải pháp đơn giản là chỉ định nó trong ràng buộc; giải pháp đơn giản là không thể bởi vì các toán tử là các phương thức tĩnh và tĩnh không thể có trong một giao diện và (đây là vấn đề) các ràng buộc được biểu diễn dưới dạng các giao diện.

Những gì C # cần là một loại ràng buộc thực sự, tất cả các giao diện cũng sẽ là các ràng buộc, nhưng không phải tất cả các ràng buộc sẽ là các giao diện thì bạn có thể làm điều này:

constraint CHasPlusEquals
{
    static CHasPlusEquals operator + (CHasPlusEquals a, CHasPlusEquals b);
}

T SumElements<T>(T initVal, T[] values) where T : CHasPlusEquals
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

Đã có rất nhiều thảo luận về việc tạo ra một số học cho tất cả các loại số để thực hiện, nhưng có mối quan tâm về hiệu quả, vì một ràng buộc không phải là một cấu trúc đa hình, tạo ra một ràng buộc số học sẽ giải quyết vấn đề đó.


Các toán tử trong C # là tĩnh, vì vậy điều này vẫn không có ý nghĩa.
John Saunders

Đó là điểm, ràng buộc xác minh rằng TYPE (không phải cá thể) có toán tử + (và bằng cách mở rộng toán tử + =) và cho phép tạo mẫu, sau đó khi thay thế mẫu thực tế xảy ra, đối tượng được sử dụng được đảm bảo của một loại có toán tử đó (hoặc phương thức tĩnh khác.) Vì vậy, bạn có thể nói: SumElements <int> (0, 5); cái sẽ khởi tạo điều này: SumElements (int initVal, int [] value) {foreach (var v in value) {initVal + = v; }}, tất nhiên, có ý nghĩa hoàn hảo
Jeremy Sorensen

1
Hãy nhớ rằng nó không phải là một mẫu. Đó là khái quát, không giống với các mẫu C ++.
John Saunders

@JohnSaunders: Generics không phải là các mẫu và tôi không biết làm thế nào chúng có thể được tạo ra một cách hợp lý để làm việc với các toán tử ràng buộc tĩnh, nhưng trong bối cảnh chung có nhiều trường hợp có thể hữu ích để xác định rằng Tnên có một tĩnh thành viên (ví dụ một nhà máy sản xuất Tcá thể). Ngay cả khi không có thay đổi thời gian chạy, tôi nghĩ có thể định nghĩa các quy ước và phương thức trợ giúp cho phép các ngôn ngữ thực hiện một thứ như đường cú pháp theo cách hiệu quả và có thể tương tác. Nếu với mỗi giao diện với các phương thức tĩnh ảo, có một lớp trợ giúp ...
supercat 14/12/13

1
@JohnSaunders: Vấn đề không phải là rất nhiều phương thức thực hiện một giao diện, mà là trình biên dịch không thể chọn một phương thức ảo để gọi mà không có một đối tượng dựa trên đó để lựa chọn. Một giải pháp là gửi cuộc gọi "giao diện tĩnh" không sử dụng công cụ ảo (không hoạt động) mà thay vào đó sử dụng cuộc gọi đến một lớp tĩnh chung. Công văn chung dựa trên loại không yêu cầu phải có một cá thể, mà chỉ có một loại.
supercat

3

Bởi vì các giao diện nằm trong cấu trúc kế thừa và các phương thức tĩnh không kế thừa tốt.


3

Những gì bạn dường như muốn sẽ cho phép một phương thức tĩnh được gọi thông qua cả Loại hoặc bất kỳ thể hiện nào của loại đó. Điều này ít nhất sẽ dẫn đến sự mơ hồ mà không phải là một đặc điểm mong muốn.

Sẽ có những cuộc tranh luận bất tận về việc nó có quan trọng không, đó là cách thực hành tốt nhất và liệu có vấn đề về hiệu suất khi thực hiện theo cách này hay cách khác. Bởi đơn giản là không hỗ trợ nó C # giúp chúng ta không phải lo lắng về nó.

Cũng có khả năng là một trình biên dịch phù hợp với mong muốn này sẽ mất một số tối ưu hóa có thể đi kèm với sự tách biệt chặt chẽ hơn giữa các phương thức tĩnh và phương thức tĩnh.


Thật thú vị, hoàn toàn có thể gọi một phương thức tĩnh bằng cả Kiểu Loại quảng cáo trong VB.Net (mặc dù IDE đưa ra cảnh báo trong trường hợp sau). Nó không có vẻ là một vấn đề. Bạn có thể đúng về những tối ưu.
Kramii

3

Bạn có thể nghĩ về các phương thức tĩnh và phương thức không tĩnh của một lớp là các giao diện khác nhau. Khi được gọi, các phương thức tĩnh phân giải thành đối tượng lớp tĩnh đơn và các phương thức không tĩnh giải quyết theo thể hiện của lớp mà bạn xử lý. Vì vậy, nếu bạn sử dụng các phương thức tĩnh và không tĩnh trong một giao diện, bạn thực sự sẽ khai báo hai giao diện khi thực sự chúng ta muốn các giao diện được sử dụng để truy cập vào một thứ gắn kết.


Đây là một POV thú vị và có lẽ là một trong những nhà thiết kế C # đã nghĩ đến. Tôi sẽ nghĩ về các thành viên tĩnh theo một cách khác từ bây giờ.
Kramii

3

Để đưa ra một ví dụ trong đó tôi đang thiếu triển khai tĩnh các phương thức giao diện hoặc những gì Mark Brackett đã giới thiệu là "phương thức loại được gọi là":

Khi đọc từ bộ lưu trữ cơ sở dữ liệu, chúng ta có một lớp DataTable chung xử lý việc đọc từ một bảng của bất kỳ cấu trúc nào. Tất cả thông tin cụ thể của bảng được đặt trong một lớp trên mỗi bảng cũng chứa dữ liệu cho một hàng từ DB và phải thực hiện giao diện IDataRow. Bao gồm trong IDataRow là một mô tả về cấu trúc của bảng để đọc từ cơ sở dữ liệu. DataTable phải yêu cầu cơ sở hạ tầng từ IDataRow trước khi đọc từ DB. Hiện tại nó trông giống như:

interface IDataRow {
  string GetDataSTructre();  // How to read data from the DB
  void Read(IDBDataRow);     // How to populate this datarow from DB data
}

public class DataTable<T> : List<T> where T : IDataRow {

  public string GetDataStructure()
    // Desired: Static or Type method:
    // return (T.GetDataStructure());
    // Required: Instantiate a new class:
    return (new T().GetDataStructure());
  }

}

Cấu trúc GetDataStr chỉ được yêu cầu một lần cho mỗi bảng để đọc, chi phí để khởi tạo thêm một thể hiện là tối thiểu. Tuy nhiên, nó sẽ tốt đẹp trong trường hợp này ở đây.


1

FYI: Bạn có thể có một hành vi tương tự như những gì bạn muốn bằng cách tạo các phương thức mở rộng cho giao diện. Phương thức mở rộng sẽ là một hành vi tĩnh không chia sẻ. Tuy nhiên, thật không may, phương pháp tĩnh này sẽ không phải là một phần của hợp đồng.


1

Các giao diện là các bộ trừu tượng của chức năng có sẵn được xác định.

Có hay không một phương thức trong giao diện đó có hoạt động như tĩnh hay không là một chi tiết triển khai cần được ẩn đằng sau giao diện . Sẽ là sai khi định nghĩa một phương thức giao diện là tĩnh bởi vì bạn sẽ không cần thiết buộc phương thức phải được thực hiện theo một cách nhất định.

Nếu các phương thức được định nghĩa là tĩnh, thì lớp thực hiện giao diện sẽ không được gói gọn như nó có thể. Đóng gói là một điều tốt để phấn đấu trong thiết kế hướng đối tượng (Tôi sẽ không đi vào lý do tại sao, bạn có thể đọc điều đó ở đây: http://en.wikipedia.org/wiki/Object-oriented ). Vì lý do này, các phương thức tĩnh không được phép trong các giao diện.


1
Có, một tĩnh trong khai báo giao diện sẽ là ngớ ngẩn. Một lớp không nên bị buộc phải thực hiện giao diện theo một cách nhất định. Tuy nhiên, C # có giới hạn một lớp để thực hiện giao diện bằng các phương thức không tĩnh. Điều này có nghĩa không? Tại sao?
Kramii

Bạn không thể sử dụng từ khóa 'tĩnh'. Không có hạn chế nào vì bạn không cần từ khóa tĩnh để viết một phương thức hoạt động tĩnh. Chỉ cần viết một phương thức không truy cập bất kỳ thành viên nào của đối tượng, sau đó nó sẽ hoạt động giống như một phương thức tĩnh. Vì vậy, có hạn chế.
Scott Langham

Đúng Scott, nhưng nó không cho phép ai đó truy cập phương thức đó theo cách tĩnh, trong một phần khác của mã. Không phải tôi có thể nghĩ ra một lý do mà bạn muốn, nhưng đó dường như là những gì OP đang yêu cầu.
Chris Marasti-Georg

Chà, nếu bạn thực sự cảm thấy cần bạn có thể viết nó như một phương thức tĩnh để truy cập vào một phần khác của mã, và chỉ cần viết phương thức không tĩnh để gọi phương thức tĩnh. Tôi có thể sai, nhưng tôi nghi ngờ đó là mục tiêu của người hỏi.
Scott Langham

1

Các lớp tĩnh sẽ có thể làm điều này để chúng có thể được sử dụng rộng rãi. Thay vào đó, tôi đã phải thực hiện một Singleton để đạt được kết quả mong muốn.

Tôi đã có một loạt các lớp Lớp doanh nghiệp tĩnh triển khai các phương thức CRUD như "Tạo", "Đọc", "Cập nhật", "Xóa" cho từng loại thực thể như "Người dùng", "Nhóm", v.v. Sau đó, tôi đã tạo một cơ sở điều khiển có thuộc tính trừu tượng cho lớp Business Layer đã triển khai các phương thức CRUD. Điều này cho phép tôi tự động hóa các hoạt động "Tạo", "Đọc", "Cập nhật", "Xóa" khỏi lớp cơ sở. Tôi đã phải sử dụng Singleton vì giới hạn Tĩnh.


1

Hầu hết mọi người dường như quên rằng trong các Lớp OOP cũng là các đối tượng và vì vậy họ có các thông báo, vì lý do nào đó c # gọi là "phương thức tĩnh". Thực tế là sự khác biệt tồn tại giữa các đối tượng thể hiện và các đối tượng lớp chỉ cho thấy những sai sót hoặc thiếu sót trong ngôn ngữ. Người lạc quan về c # mặc dù ...


2
Vấn đề là câu hỏi này không phải là "trong OOP". Đó là về "trong C #". Trong C #, không có "tin nhắn".
John Saunders

1

OK ở đây là một ví dụ về việc cần một 'phương thức loại'. Tôi đang tạo một trong các nhóm các lớp dựa trên một số XML nguồn. Vì vậy, tôi có một

  static public bool IsHandled(XElement xml)

chức năng được gọi lần lượt trên mỗi lớp.

Hàm nên tĩnh vì nếu không chúng ta sẽ lãng phí thời gian để tạo các đối tượng không phù hợp. Như @Ian Boyde chỉ ra rằng nó có thể được thực hiện trong một lớp học, nhưng điều này chỉ làm tăng thêm sự phức tạp.

Sẽ rất tốt nếu thêm nó vào giao diện để buộc những người triển khai lớp thực hiện nó. Điều này sẽ không gây ra chi phí đáng kể - nó chỉ là kiểm tra thời gian biên dịch / liên kết và không ảnh hưởng đến vtable.

Tuy nhiên, nó cũng sẽ là một cải tiến khá nhỏ. Vì phương thức là tĩnh, tôi là người gọi, phải gọi nó một cách rõ ràng và do đó nhận được một lỗi biên dịch ngay lập tức nếu nó không được thực hiện. Cho phép nó được chỉ định trên giao diện có nghĩa là lỗi này xuất hiện sớm hơn một chút trong chu kỳ phát triển, nhưng điều này là không đáng kể so với các sự cố giao diện bị hỏng khác.

Vì vậy, nó là một tính năng tiềm năng nhỏ mà trên cân bằng có lẽ là tốt nhất bỏ qua.


1

Việc một lớp tĩnh được Microsoft triển khai trong C # bởi Microsoft tạo ra một thể hiện đặc biệt của một lớp với các phần tử tĩnh chỉ là một sự kỳ quặc về cách đạt được chức năng tĩnh. Nó không phải là một điểm lý thuyết.

Một giao diện NÊN là một mô tả của giao diện lớp - hoặc cách nó được tương tác với, và nó sẽ bao gồm các tương tác tĩnh. Định nghĩa chung về giao diện (từ Meriam-Webster): địa điểm hoặc khu vực nơi những thứ khác nhau gặp gỡ và giao tiếp với nhau hoặc ảnh hưởng lẫn nhau. Khi bạn bỏ hoàn toàn các thành phần tĩnh của một lớp hoặc các lớp tĩnh hoàn toàn, chúng ta sẽ bỏ qua các phần lớn về cách các chàng trai xấu này tương tác.

Dưới đây là một ví dụ rất rõ ràng về việc nơi có thể sử dụng các giao diện với các lớp tĩnh sẽ khá hữu ích:

public interface ICrudModel<T, Tk>
{
    Boolean Create(T obj);
    T Retrieve(Tk key);
    Boolean Update(T obj);
    Boolean Delete(T obj);
}

Hiện tại, tôi viết các lớp tĩnh có chứa các phương thức này mà không có bất kỳ loại kiểm tra nào để đảm bảo rằng tôi đã không quên bất cứ điều gì. Giống như ngày xưa tồi tệ của lập trình trước OOP.


Đây là một câu hỏi cổ xưa; những ngày này, chúng tôi rất cố gắng để tránh các câu hỏi dựa trên ý kiến ​​vì chúng khó trả lời một cách có thẩm quyền. Cách tốt nhất để trả lời điều này là mô tả lý do MS có, hoặc đáng lẽ phải có, vì đã làm theo cách họ đã làm.
Nathan Tuggy

1

C # và CLR nên hỗ trợ các phương thức tĩnh trong các giao diện như Java. Công cụ sửa đổi tĩnh là một phần của định nghĩa hợp đồng và có ý nghĩa, cụ thể là hành vi và giá trị trả về không thay đổi tùy theo cơ sở mặc dù nó vẫn có thể thay đổi theo cuộc gọi.

Điều đó nói rằng, tôi khuyên rằng khi bạn muốn sử dụng một phương thức tĩnh trong một giao diện và không thể, thay vào đó hãy sử dụng một chú thích. Bạn sẽ nhận được các chức năng bạn đang tìm kiếm.


0

Tôi nghĩ rằng câu trả lời ngắn gọn là "bởi vì nó không có ích". Để gọi một phương thức giao diện, bạn cần một thể hiện của loại. Từ các phương thức cá thể, bạn có thể gọi bất kỳ phương thức tĩnh nào bạn muốn.


0

Tôi nghĩ rằng câu hỏi đang nhận được là C # cần một từ khóa khác, chính xác là loại tình huống này. Bạn muốn một phương thức có giá trị trả về chỉ phụ thuộc vào kiểu mà nó được gọi. Bạn không thể gọi nó là "tĩnh" nếu không biết loại nói. Nhưng một khi loại được biết đến, nó sẽ trở thành tĩnh. "Tĩnh chưa giải quyết" là ý tưởng - nó chưa tĩnh, nhưng một khi chúng ta biết loại nhận, nó sẽ là. Đây là một khái niệm hoàn toàn tốt, đó là lý do tại sao các lập trình viên tiếp tục yêu cầu nó. Nhưng nó không hoàn toàn phù hợp với cách các nhà thiết kế nghĩ về ngôn ngữ.

Vì nó không có sẵn, tôi đã sử dụng các phương thức không tĩnh theo cách hiển thị bên dưới. Không chính xác lý tưởng, nhưng tôi không thể thấy bất kỳ cách tiếp cận nào có ý nghĩa hơn, ít nhất là không phải với tôi.

public interface IZeroWrapper<TNumber> {
  TNumber Zero {get;}
}

public class DoubleWrapper: IZeroWrapper<double> {
  public double Zero { get { return 0; } }
}

0

Theo Giao diện khái niệm hướng đối tượng được thực hiện bởi các lớp và có hợp đồng để truy cập các hàm (hoặc phương thức) được thực hiện này bằng cách sử dụng đối tượng.

Vì vậy, nếu bạn muốn truy cập các phương thức Hợp đồng giao diện, bạn phải tạo đối tượng. Luôn luôn không được phép trong trường hợp phương thức Tĩnh. Các lớp, phương thức và biến tĩnh không bao giờ yêu cầu các đối tượng và tải trong bộ nhớ mà không tạo đối tượng của khu vực đó (hoặc lớp) hoặc bạn có thể nói không yêu cầu Tạo đối tượng.


0

Về mặt khái niệm , không có lý do tại sao một giao diện không thể xác định hợp đồng bao gồm các phương thức tĩnh.

Đối với việc triển khai ngôn ngữ C # hiện tại, hạn chế là do sự cho phép kế thừa của một lớp cơ sở và giao diện. Nếu "class someBaseClass" thực hiện giao diện "ISomeInterface" và "class someDerivingClass: someBaseClass, ISomeInterface" cũng thực hiện giao diện, một phương thức tĩnh để thực hiện một phương thức giao diện sẽ không thể biên dịch vì một phương thức tĩnh có thể có cùng chữ ký như một phương thức cá thể có mặt trong lớp cơ sở để thực hiện giao diện).

Một lớp tĩnh có chức năng giống hệt như một singleton và phục vụ cùng một mục đích như một singleton với cú pháp sạch hơn. Vì một singleton có thể thực hiện một giao diện, việc triển khai giao diện theo số liệu thống kê là hợp lệ về mặt khái niệm.

Vì vậy, nó chỉ đơn giản là nắm bắt được giới hạn của xung đột tên C # chẳng hạn và các phương thức tĩnh có cùng tên trên toàn bộ kế thừa. Không có lý do tại sao C # không thể được "nâng cấp" để hỗ trợ các hợp đồng (giao diện) phương thức tĩnh.


-1

Khi một lớp thực hiện một giao diện, nó đang tạo cá thể cho các thành viên giao diện. Mặc dù kiểu tĩnh không có thể hiện, nhưng không có điểm nào có chữ ký tĩnh trong giao diện.

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.