Tại sao tôi không thể có các phương thức tĩnh trừu tượng trong C #?


182

Gần đây tôi đã làm việc với các nhà cung cấp một cách công bằng và tôi đã gặp một tình huống thú vị khi tôi muốn có một lớp trừu tượng có một phương thức tĩnh trừu tượng. Tôi đọc một vài bài viết về chủ đề này, và nó có ý nghĩa, nhưng có một lời giải thích rõ ràng tốt đẹp?


3
Vui lòng để lại những mở để cho phép cải tiến trong tương lai.
Đánh dấu Biek

6
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ữ.
William Jockusch 20/03/2015

@WilliamJockusch loại nhận có nghĩa là gì? Nếu tôi gọi BaseClass.StaticMethod () thì BaseClass là loại duy nhất nó có thể sử dụng để đưa ra quyết định. Nhưng ở cấp độ này, nó là trừu tượng nên phương pháp không thể được giải quyết. Thay vào đó, nếu bạn gọi DeruredClass.StaticMethod thì lớp cơ sở là không liên quan.
Martin Capodici

Trong lớp cơ sở, phương thức này chưa được giải quyết và bạn không thể sử dụng nó. Bạn cần một loại dẫn xuất hoặc một đối tượng (lần lượt sẽ có một loại dẫn xuất). Bạn sẽ có thể gọi baseClassObject.Method () hoặc DeruredClass.Method (). Bạn không thể gọi BaseClass.Method () vì điều đó không cung cấp cho bạn loại.
William Jockusch

Câu trả lời:


157

Các phương thức tĩnh không được khởi tạo như vậy, chúng chỉ khả dụng mà không cần tham chiếu đối tượng.

Một cuộc gọi đến một phương thức tĩnh được thực hiện thông qua tên lớp, không phải thông qua một tham chiếu đối tượng và mã Ngôn ngữ trung gian (IL) để gọi nó sẽ gọi phương thức trừu tượng thông qua tên của lớp đã định nghĩa nó, không nhất thiết phải là tên của lớp bạn đã sử dụng.

Hãy để tôi chỉ ra một ví dụ.

Với đoạn mã sau:

public class A
{
    public static void Test()
    {
    }
}

public class B : A
{
}

Nếu bạn gọi B.Test, như thế này:

class Program
{
    static void Main(string[] args)
    {
        B.Test();
    }
}

Sau đó, mã thực tế bên trong phương thức Main như sau:

.entrypoint
.maxstack 8
L0000: nop 
L0001: call void ConsoleApplication1.A::Test()
L0006: nop 
L0007: ret 

Như bạn có thể thấy, cuộc gọi được thực hiện cho A.Test, vì đó là lớp A đã định nghĩa nó chứ không phải B.Test, mặc dù bạn có thể viết mã theo cách đó.

Nếu bạn có các loại lớp , như trong Delphi, nơi bạn có thể tạo một biến tham chiếu đến một loại chứ không phải một đối tượng, bạn sẽ sử dụng nhiều hơn cho các phương thức tĩnh ảo và do đó trừu tượng (và cả các hàm tạo), nhưng chúng không có sẵn và do đó, các cuộc gọi tĩnh là không ảo trong .NET.

Tôi nhận ra rằng các nhà thiết kế IL có thể cho phép mã được biên dịch để gọi B.Test và giải quyết cuộc gọi khi chạy, nhưng nó vẫn không phải là ảo, vì bạn vẫn sẽ phải viết một loại tên lớp ở đó.

Các phương thức ảo, và do đó là các phương thức trừu tượng, chỉ hữu ích khi bạn đang sử dụng một biến, trong thời gian chạy, có thể chứa nhiều loại đối tượng khác nhau và do đó bạn muốn gọi đúng phương thức cho đối tượng hiện tại mà bạn có trong biến. Với các phương thức tĩnh, dù sao bạn cũng cần phải đi qua một tên lớp, vì vậy phương thức chính xác để gọi được biết đến vào thời gian biên dịch vì nó không thể và sẽ không thay đổi.

Vì vậy, các phương thức tĩnh ảo / trừu tượng không có sẵn trong .NET.


4
Kết hợp với cách thức quá tải toán tử được thực hiện trong C #, điều này không may loại bỏ khả năng yêu cầu các lớp con cung cấp một triển khai cho quá tải toán tử đã cho.
Chris Moschini

23
Tôi không thấy câu trả lời này hữu ích khủng khiếp vì định nghĩa của Test()Athay vì trừu tượng và có khả năng được định nghĩa trong B. \

5
Các tham số loại chung hoạt động hiệu quả như các biến "loại" không bền và các phương thức tĩnh ảo có thể hữu ích trong ngữ cảnh như vậy. Ví dụ: nếu một Carloại có một CreateFromDescriptionphương thức nhà máy tĩnh ảo , thì mã chấp nhận Carloại chung chung Tcó liên quan có thể gọi T.CreateFromDescriptionđể sản xuất một loại xe hơi T. Cấu trúc như vậy có thể được hỗ trợ khá tốt trong CLR nếu mỗi loại xác định phương thức đó giữ một thể hiện đơn lẻ tĩnh của một lớp chung lồng nhau giữ các phương thức "tĩnh" ảo.
supercat

45

Các phương thức tĩnh không thể được kế thừa hoặc ghi đè và đó là lý do tại sao chúng không thể trừu tượng. Vì các phương thức tĩnh được định nghĩa trên kiểu chứ không phải thể hiện của một lớp, nên chúng phải được gọi rõ ràng trên kiểu đó. Vì vậy, khi bạn muốn gọi một phương thức trên một lớp con, bạn cần sử dụng tên của nó để gọi nó. Điều này làm cho sự kế thừa không liên quan.

Giả sử bạn có thể, trong một khoảnh khắc, kế thừa các phương thức tĩnh. Hãy tưởng tượng kịch bản này:

public static class Base
{
    public static virtual int GetNumber() { return 5; }
}

public static class Child1 : Base
{
    public static override int GetNumber() { return 1; }
}

public static class Child2 : Base
{
    public static override int GetNumber() { return 2; }
}

Nếu bạn gọi Base.GetNumber (), phương thức nào sẽ được gọi? Giá trị nào được trả về? Nó khá dễ dàng để thấy rằng không tạo ra các thể hiện của các đối tượng, kế thừa là khá khó khăn. Các phương thức trừu tượng không có sự kế thừa chỉ là các phương thức không có cơ thể, vì vậy không thể được gọi.


33
Với kịch bản của bạn, tôi sẽ nói Base.GetNumber () sẽ trả về 5; Child1.GetNumber () trả về 1; Child2.GetNumber () trả về 2; Bạn có thể chứng minh tôi sai, để giúp tôi hiểu lý lẽ của bạn? Cảm ơn bạn
Luis Filipe

Khuôn mặt mà bạn nghĩ Base.GetNumber () trả về 5, có nghĩa là bạn đã hiểu chuyện gì đang xảy ra. Bằng cách trả về giá trị cơ sở, không có sự kế thừa đang diễn ra.
David Wengier

60
Tại sao trên thế giới Base.GetNumber () sẽ trả lại bất cứ thứ gì khác ngoài 5? Đó là một phương thức trong lớp cơ sở - chỉ có 1 tùy chọn ở đó.
Artem Russakovskii

4
@ArtemRussakovskii: Giả sử một người đã có int DoSomething<T>() where T:Base {return T.GetNumber();}. Nó có vẻ hữu ích nếu DoSomething<Base>()có thể trả lại năm, trong khi DoSomething<Child2>()sẽ trả lại hai. Khả năng như vậy không chỉ hữu ích cho các ví dụ về đồ chơi, mà còn cho những thứ như class Car {public static virtual Car Build(PurchaseOrder PO);}, trong đó mọi lớp xuất phát từ Carsẽ phải xác định một phương thức có thể xây dựng một thể hiện cho một đơn đặt hàng.
supercat

4
Có chính xác "vấn đề" với thừa kế không tĩnh.
Ark-kun

18

Một người trả lời khác (McDowell) nói rằng đa hình chỉ hoạt động cho các trường hợp đối tượng. Điều đó cần phải có trình độ; có những ngôn ngữ coi các lớp là thể hiện của loại "Lớp" hoặc "Siêu dữ liệu". Các ngôn ngữ này hỗ trợ đa hình cho cả hai phương thức thể hiện và lớp (tĩnh).

C #, giống như Java và C ++ trước đó, không phải là một ngôn ngữ như vậy; các statictừ khóa được sử dụng một cách rõ ràng để biểu thị rằng phương pháp này là tĩnh-bound chứ không phải năng động / ảo.


9

Đây là một tình huống chắc chắn cần có sự kế thừa cho các trường và phương thức tĩnh:

abstract class Animal
{
  protected static string[] legs;

  static Animal() {
    legs=new string[0];
  }

  public static void printLegs()
  {
    foreach (string leg in legs) {
      print(leg);
    }
  }
}


class Human: Animal
{
  static Human() {
    legs=new string[] {"left leg", "right leg"};
  }
}


class Dog: Animal
{
  static Dog() {
    legs=new string[] {"left foreleg", "right foreleg", "left hindleg", "right hindleg"};
  }
}


public static void main() {
  Dog.printLegs();
  Human.printLegs();
}


//what is the output?
//does each subclass get its own copy of the array "legs"?

4
Không, chỉ có một ví dụ của mảng 'chân'. Đầu ra là không đặc biệt vì bạn không biết các hàm xây dựng tĩnh sẽ được gọi theo thứ tự nào (thực sự không có gì đảm bảo hàm tạo tĩnh của lớp cơ sở sẽ được gọi cả). 'Cần' là một thuật ngữ khá tuyệt đối trong đó 'mong muốn' có lẽ chính xác hơn.
Sam

legsnên là một thuộc tính trừu tượng tĩnh.
Little Endian

8

Để thêm vào các giải thích trước đó, các cuộc gọi phương thức tĩnh được liên kết với một phương thức cụ thể tại thời gian biên dịch , thay vào đó loại trừ hành vi đa hình.


C # được gõ tĩnh; các cuộc gọi đến các phương thức đa hình cũng bị ràng buộc tại thời gian biên dịch khi tôi hiểu nó - nghĩa là CLR không còn lại để giải quyết phương thức nào sẽ gọi trong thời gian chạy.
Adam Tolley

Vậy chính xác thì bạn nghĩ đa hình hoạt động như thế nào trên CLR? Giải thích của bạn chỉ loại trừ công văn ảo.
Rytmis

Đó không thực sự là một bình luận hữu ích như nó có thể. Tôi đã mời (với 'như tôi hiểu') bài diễn văn hữu ích, nghĩ rằng có lẽ bạn có thể cung cấp thêm một chút nội dung - nhìn thấy khi mọi người đến đây để tìm câu trả lời và không lăng mạ. Mặc dù, có vẻ như tôi có thể có tội với điều tương tự - tôi thực sự có ý nhận xét ở trên như một câu hỏi: Không C # đánh giá những điều này vào thời gian biên dịch?
Adam Tolley

Xin lỗi, tôi không có ý xúc phạm (mặc dù tôi thừa nhận đã trả lời một cách lén lút ;-). Điểm của câu hỏi của tôi là, nếu bạn đã có các lớp này: class Base {public virtual void Phương thức (); } lớp Derogen: Base {công khai ghi đè void Phương thức (); } và viết như vậy: Base dụ = new Derogen (); dụ.Method (); thông tin loại thời gian biên dịch trên trang web cuộc gọi là chúng ta đã có một phiên bản của Base, khi phiên bản thực tế là Derogen. Vì vậy, trình biên dịch không thể giải quyết phương thức chính xác để gọi. Thay vào đó, nó phát ra một lệnh IL "callvirt" cho biết thời gian chạy để gửi đi ..
Rytmis

1
Cảm ơn người đàn ông, đó là thông tin! Đoán tôi đã được lặn xuống IL đủ lâu, chúc tôi may mắn.
Adam Tolley

5

Chúng tôi thực sự ghi đè các phương thức tĩnh (trong delphi), nó hơi xấu, nhưng nó hoạt động tốt cho nhu cầu của chúng tôi.

Chúng tôi sử dụng nó để các lớp có thể có một danh sách các đối tượng có sẵn của chúng mà không cần thể hiện của lớp, ví dụ, chúng tôi có một phương thức giống như sau:

class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end; 

Thật xấu xí nhưng cần thiết, theo cách này chúng ta có thể khởi tạo ngay những gì cần thiết, thay vì tất cả các lớp được tạo ngay lập tức chỉ để tìm kiếm các đối tượng có sẵn.

Đây là một ví dụ đơn giản, nhưng bản thân ứng dụng là một ứng dụng máy chủ-máy khách có tất cả các lớp có sẵn trong một máy chủ và nhiều máy khách khác nhau có thể không cần mọi thứ mà máy chủ có và sẽ không bao giờ cần đối tượng.

Vì vậy, điều này dễ bảo trì hơn nhiều so với việc có một ứng dụng máy chủ khác nhau cho mỗi khách hàng.

Hy vọng ví dụ đã rõ ràng.


0

Các phương thức trừu tượng là ngầm ảo. Các phương thức trừu tượng yêu cầu một thể hiện, nhưng các phương thức tĩnh không có một thể hiện. Vì vậy, bạn có thể có một phương thức tĩnh trong một lớp trừu tượng, nó chỉ không thể là trừu tượng tĩnh (hoặc trừu tượng tĩnh).


1
-1 phương thức ảo không cần một thể hiện, ngoại trừ theo thiết kế. Và bạn không thực sự giải quyết câu hỏi, nhiều như làm chệch hướng nó.
FallenAvatar
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.