Tại sao tôi không thể kế thừa các lớp tĩnh?


224

Tôi có một vài lớp không thực sự cần bất kỳ tiểu bang nào. Từ quan điểm tổ chức, tôi muốn đưa chúng vào hệ thống phân cấp.

Nhưng có vẻ như tôi không thể khai báo kế thừa cho các lớp tĩnh.

Một cái gì đó như thế:

public static class Base
{
}

public static class Inherited : Base
{
}

sẽ không làm việc.

Tại sao các nhà thiết kế của ngôn ngữ đóng cửa khả năng đó?


Câu trả lời:


174

Trích dẫn từ đây :

Điều này thực sự là do thiết kế. Dường như không có lý do chính đáng để kế thừa một lớp tĩnh. Nó có các thành viên tĩnh công khai mà bạn luôn có thể truy cập thông qua tên lớp. Những lý do duy nhất tôi thấy để kế thừa các công cụ tĩnh là những lý do xấu, chẳng hạn như lưu một vài ký tự gõ.

Có thể có lý do để xem xét các cơ chế để đưa các thành viên tĩnh trực tiếp vào phạm vi (và thực tế chúng ta sẽ xem xét điều này sau chu kỳ sản phẩm của Orcas), nhưng kế thừa lớp tĩnh không phải là cách để sử dụng: Đó là cơ chế sử dụng sai và hoạt động chỉ dành cho các thành viên tĩnh xảy ra trong một lớp tĩnh.

(Mads Torgersen, C # Ngôn ngữ PM)

Ý kiến ​​khác từ kênh9

Kế thừa trong .NET chỉ hoạt động trên cơ sở cá thể. Các phương thức tĩnh được định nghĩa ở cấp độ kiểu không phải ở cấp độ thể hiện. Đó là lý do tại sao ghi đè không hoạt động với các phương thức / thuộc tính / sự kiện tĩnh ...

Các phương thức tĩnh chỉ được giữ một lần trong bộ nhớ. Không có bảng ảo, vv được tạo ra cho họ.

Nếu bạn gọi một phương thức cá thể trong .NET, bạn luôn cung cấp cho nó phương thức hiện tại. Điều này bị ẩn bởi thời gian chạy .NET, nhưng nó xảy ra. Mỗi phương thức cá thể có đối số đầu tiên là một con trỏ (tham chiếu) đến đối tượng mà phương thức được chạy. Điều này không xảy ra với các phương thức tĩnh (vì chúng được xác định ở cấp độ loại). Trình biên dịch nên quyết định chọn phương thức để gọi như thế nào?

(littleguru)

Và như một ý tưởng có giá trị, littleguru có một "cách giải quyết" một phần cho vấn đề này: mẫu Singleton .


92
Thiếu trí tưởng tượng về phía Torgersen, thực sự. Tôi có một lý do khá chính đáng khi muốn làm điều này :)
Thorarin

10
Còn cái này thì sao? Bạn có một dll không phải là nguồn mở / bạn không có quyền truy cập vào nguồn đó, giả sử NUnit. Bạn muốn mở rộng lớp Assert của mình để có một phương thức như Ném <> (Hành động a) (vâng, cũng vậy, nhưng tại một thời điểm thì không.) Bạn có thể thêm vào dự án của mình một Assert: NUnit.Framework.Assert lớp, và sau đó thêm một phương thức Ném. Vấn đề được giải quyết. Nó cũng dễ sử dụng hơn trong mã ... thay vì phải đưa ra một tên lớp khác và nhớ tên lớp đó, chỉ cần gõ Assert. và xem các phương pháp có sẵn.
user420667

4
@KonradMorawski Không thể viết các phương thức mở rộng cho các lớp tĩnh. stackoverflow.com/questions/249222/ trộm
Martin Braun

2
@modiX Tất nhiên rồi. Bạn hiểu lầm tôi ở đây. Điều tôi muốn nói là chức năng được yêu cầu trong kịch bản được mô tả bởi user420667 có thể được cung cấp ( trong tương lai ) ít nhất là cho phép các phương thức mở rộng cho các lớp tĩnh. Không cần thừa kế tĩnh. Đó là lý do tại sao kịch bản của anh ấy không gây ấn tượng với tôi như là một lý do rất tốt để giới thiệu kế thừa tĩnh. Nếu C # được sửa đổi trong khía cạnh này, tại sao lại đi thiết kế lại chính khi một thứ yếu sẽ tốt như trong thực tế.
Konrad Morawski

5
@Learner Điều đó không thực sự công bằng. Trong .Net mọi thứ được kế thừa từ objectvà mọi người đều mong muốn biết điều đó. Vì vậy, các lớp tĩnh luôn kế thừa từ object, cho dù bạn xác định rõ ràng hay không.
RenniePet

71

Lý do chính mà bạn không thể kế thừa một lớp tĩnh là chúng trừu tượng và kín (điều này cũng ngăn chặn bất kỳ trường hợp nào của chúng được tạo ra).

Vậy đây:

static class Foo { }

biên dịch cho IL này:

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }

17
Bạn đang nói rằng tĩnh được thực hiện dưới dạng trừu tượng + niêm phong. Anh ta muốn biết tại sao điều này đã được thực hiện. Tại sao nó được niêm phong?
Lucas

22
Điều này không trả lời tất cả các câu hỏi; nó chỉ nhắc lại vấn đề mà anh ta đang hỏi
Igby Largeeman

28
Chà, OP nên đã hỏi "Tại sao các lớp tĩnh bị niêm phong", chứ không phải "Tại sao tôi không được thừa hưởng từ các lớp tĩnh?", Câu trả lời tất nhiên là "vì chúng bị niêm phong" . Câu trả lời này được bình chọn của tôi.
Alex Budovski

6
cảm ơn vì đã chia sẻ rằng các lớp tĩnh được tự động biên dịch thành các lớp niêm phong - Tôi đã tự hỏi làm thế nào / khi điều đó xảy ra!
Đánh dấu

23
@AlexBudovski: Câu trả lời này đúng nhưng vô dụng như nói với một người lạc lối "bạn đang ở trước mặt tôi" khi họ hỏi bạn "tôi đang ở đâu".
DeepSpace101

24

Nghĩ về nó theo cách này: bạn truy cập các thành viên tĩnh thông qua tên loại, như thế này:

MyStaticType.MyStaticMember();

Nếu bạn được thừa kế từ lớp đó, bạn sẽ phải truy cập nó thông qua tên loại mới:

MyNewType.MyStaticMember();

Do đó, mục mới không có mối quan hệ với bản gốc khi được sử dụng trong mã. Sẽ không có cách nào để tận dụng bất kỳ mối quan hệ thừa kế nào cho những thứ như đa hình.

Có lẽ bạn đang nghĩ rằng bạn chỉ muốn mở rộng một số mục trong lớp gốc. Trong trường hợp đó, không có gì ngăn cản bạn chỉ sử dụng một thành viên của bản gốc trong một loại hoàn toàn mới.

Có lẽ bạn muốn thêm các phương thức vào một kiểu tĩnh hiện có. Bạn có thể làm điều đó thông qua các phương pháp mở rộng.

Có lẽ bạn muốn có thể truyền một tĩnh Typecho một hàm trong thời gian chạy và gọi một phương thức trên kiểu đó, mà không biết chính xác phương thức đó làm gì. Trong trường hợp đó, bạn có thể sử dụng Giao diện.

Vì vậy, cuối cùng bạn không thực sự đạt được bất cứ điều gì từ việc kế thừa các lớp tĩnh.


5
Bạn không thể thêm các phương thức vào một kiểu tĩnh hiện có thông qua một phương thức mở rộng. Xin vui lòng xem nhận xét của tôi về câu trả lời được chấp nhận cho một ví dụ sử dụng mong muốn. (Về cơ bản, bạn chỉ muốn đổi tên những gì bạn đã đặt tên MyNewType thành MyStaticType, vì vậy MyStaticType: OldProject.MyStaticType)
user420667

2
Người ta có thể định nghĩa các quan hệ thừa kế với các kiểu tĩnh và các thành viên tĩnh ảo theo cách mà một người SomeClass<T> where T:Foocó thể truy cập các thành viên của Foo, và nếu Tlà một lớp áp đảo một số thành viên tĩnh ảo Foo, thì lớp chung sẽ sử dụng các phần ghi đè đó. Thậm chí có thể định nghĩa một quy ước thông qua đó các ngôn ngữ có thể làm như vậy theo kiểu tương thích với CLR hiện tại (ví dụ: một lớp có các thành viên như vậy nên xác định một lớp không tĩnh được bảo vệ có chứa các thành viên thể hiện đó, cùng với trường tĩnh giữ một thể hiện của loại đó).
supercat

Tôi thấy mình với một trường hợp mà tôi tin rằng việc kế thừa một lớp tĩnh là điều nên làm, mặc dù rõ ràng bạn vẫn có thể truy cập chúng. Tôi có một lớp tĩnh để gửi luồng dữ liệu thô đến máy in (để nói chuyện với máy in nhãn công nghiệp). Nó có một loạt các khai báo API windows. Bây giờ tôi thấy mình đang viết một lớp khác để gây rối với các máy in cần các lệnh gọi API tương tự. Phối hợp? Không tốt. Khai báo chúng hai lần? Xấu xí. Thừa kế? Tốt, nhưng không được phép.
Loren Pechtel

3

Những gì bạn muốn đạt được bằng cách sử dụng hệ thống phân cấp lớp chỉ có thể đạt được thông qua không gian tên. Vì vậy, các ngôn ngữ hỗ trợ các bảng tên (như C #) sẽ không sử dụng việc thực hiện phân cấp lớp của các lớp tĩnh. Vì bạn không thể khởi tạo bất kỳ lớp nào, tất cả những gì bạn cần là một tổ chức phân cấp các định nghĩa lớp mà bạn có thể có được thông qua việc sử dụng các không gian tên


Tôi có một trình nạp chung nhận một lớp dưới dạng. Lớp đó có 5 phương thức trợ giúp và 2 phương thức trả về một chuỗi (tên và mô tả). Tôi có thể đã chọn sai (tôi nghĩ vậy), nhưng giải pháp duy nhất tôi tìm thấy là khởi tạo lớp trong trình nạp chung, để truy cập các phương thức ... meh.
ANeves

Thay thế sẽ là một từ điển khổng lồ, tôi đoán. Ngoài ra, khi bạn nói "những gì bạn muốn đạt được", bạn nên nói "những gì bạn dường như đang hướng tới" hoặc một cái gì đó tương tự.
ANeves

3

Bạn có thể sử dụng thành phần thay vì ... điều này sẽ cho phép bạn truy cập các đối tượng lớp từ kiểu tĩnh. Nhưng vẫn không thể thực hiện các giao diện hoặc các lớp trừu tượng


2

Mặc dù bạn có thể truy cập các thành viên tĩnh "được kế thừa" thông qua tên các lớp được kế thừa, các thành viên tĩnh không thực sự được kế thừa. Đây là một phần lý do tại sao chúng không thể là ảo hoặc trừu tượng và không thể bị ghi đè. Trong ví dụ của bạn, nếu bạn đã khai báo Base.Method (), trình biên dịch sẽ ánh xạ một cuộc gọi đến Inherited.Method () trở lại Base.Method (). Bạn cũng có thể gọi Base.Method () một cách rõ ràng. Bạn có thể viết một bài kiểm tra nhỏ và xem kết quả với Reflector.

Vậy ... nếu bạn không thể kế thừa các thành viên tĩnh và nếu các lớp tĩnh chỉ có thể chứa các thành viên tĩnh, thì việc kế thừa một lớp tĩnh sẽ có ích gì?


1

Hmmm ... sẽ khác nhiều nếu bạn chỉ có các lớp không tĩnh chứa đầy các phương thức tĩnh ..?


2
Đó là những gì còn lại với tôi. Tôi làm theo cách này. Và tôi không thích nó.
Người dùng

Tôi cũng đã làm theo cách này, nhưng vì một số lý do, nó không mang tính thẩm mỹ khi bạn biết rằng bạn sẽ không bao giờ khởi tạo đối tượng. Tôi luôn thống khổ về các chi tiết như thế này, mặc dù thực tế là không có chi phí vật chất của nó.
gdbj

Trong lớp không tĩnh chứa đầy các phương thức tĩnh, bạn không thể đặt các phương thức mở rộng :)
Jakub Szułakiewicz

1

Một cách giải quyết bạn có thể làm là không sử dụng các lớp tĩnh mà ẩn hàm tạo để các thành viên tĩnh của lớp là thứ duy nhất có thể truy cập bên ngoài lớp. Kết quả là một lớp "tĩnh" kế thừa về cơ bản:

public class TestClass<T>
{
    protected TestClass()
    { }

    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public class TestClass : TestClass<double>
{
    // Inherited classes will also need to have protected constructors to prevent people from creating instances of them.
    protected TestClass()
    { }
}

TestClass.Add(3.0, 4.0)
TestClass<int>.Add(3, 4)

// Creating a class instance is not allowed because the constructors are inaccessible.
// new TestClass();
// new TestClass<int>();

Thật không may vì giới hạn ngôn ngữ "theo thiết kế" mà chúng ta không thể làm:

public static class TestClass<T>
{
    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public static class TestClass : TestClass<double>
{
}

1

Bạn có thể làm một cái gì đó sẽ trông giống như kế thừa tĩnh.

Đây là mẹo:

public abstract class StaticBase<TSuccessor>
    where TSuccessor : StaticBase<TSuccessor>, new()
{
    protected static readonly TSuccessor Instance = new TSuccessor();
}

Sau đó, bạn có thể làm điều này:

public class Base : StaticBase<Base>
{
    public Base()
    {
    }

    public void MethodA()
    {
    }
}

public class Inherited : Base
{
    private Inherited()
    {
    }

    public new static void MethodA()
    {
        Instance.MethodA();
    }
}

Các Inheritedlớp học không phải là tĩnh bản thân, nhưng chúng tôi không cho phép để tạo ra nó. Nó thực sự đã kế thừa hàm tạo tĩnh, nó xây dựng Basevà tất cả các thuộc tính và phương thức Basekhả dụng dưới dạng tĩnh. Bây giờ, điều duy nhất còn lại để tạo các hàm bao tĩnh cho mỗi phương thức và thuộc tính bạn cần để hiển thị cho bối cảnh tĩnh của bạn.

Có những nhược điểm như nhu cầu tạo thủ công các phương thức và newtừ khóa bao bọc tĩnh . Nhưng phương pháp này giúp hỗ trợ một cái gì đó thực sự tương tự như kế thừa tĩnh.

PS Chúng tôi đã sử dụng điều này để tạo các truy vấn đã biên dịch và điều này thực sự có thể được thay thế bằng ConcurrencyDipedia, nhưng trường chỉ đọc tĩnh với độ an toàn của luồng là đủ tốt.


1

Câu trả lời của tôi: sự lựa chọn thiết kế kém. ;-)

Đây là một cuộc tranh luận thú vị tập trung vào tác động cú pháp. Cốt lõi của đối số, theo quan điểm của tôi, là một quyết định thiết kế dẫn đến các lớp tĩnh được niêm phong. Tập trung vào tính minh bạch của tên lớp tĩnh xuất hiện ở cấp cao nhất thay vì ẩn ('khó hiểu') đằng sau tên con? Người ta có thể hình ảnh một triển khai ngôn ngữ có thể truy cập trực tiếp vào cơ sở hoặc trẻ em, gây nhầm lẫn.

Một ví dụ giả, giả sử kế thừa tĩnh được định nghĩa theo một cách nào đó.

public static class MyStaticBase
{
    SomeType AttributeBase;
}

public static class MyStaticChild : MyStaticBase
{
    SomeType AttributeChild;
}

Sẽ dẫn đến:

 // ...
 DoSomethingTo(MyStaticBase.AttributeBase);
// ...

cái nào có thể (sẽ?) tác động đến việc lưu trữ giống như

// ...
DoSomethingTo(MyStaticChild.AttributeBase);
// ...

Rất bối rối!

Nhưng chờ đã! Trình biên dịch sẽ xử lý MyStaticBase và MyStaticChild như thế nào có cùng chữ ký được xác định trong cả hai? Nếu đứa trẻ ghi đè hơn ví dụ trên của tôi sẽ KHÔNG thay đổi cùng một bộ lưu trữ, có thể? Điều này dẫn đến sự nhầm lẫn nhiều hơn.

Tôi tin rằng có một sự biện minh không gian thông tin mạnh mẽ cho sự kế thừa tĩnh hạn chế. Thêm về các giới hạn trong thời gian ngắn. Mã giả này hiển thị giá trị:

public static class MyStaticBase<T>
{
   public static T Payload;
   public static void Load(StorageSpecs);
   public static void Save(StorageSpecs);
   public static SomeType AttributeBase
   public static SomeType MethodBase(){/*...*/};
}

Sau đó, bạn nhận được:

public static class MyStaticChild : MyStaticBase<MyChildPlayloadType>
{
   public static SomeType AttributeChild;
   public static SomeType SomeChildMethod(){/*...*/};
   // No need to create the PlayLoad, Load(), and Save().
   // You, 'should' be prevented from creating them, more on this in a sec...
} 

Cách sử dụng trông như:

// ...
MyStaticChild.Load(FileNamePath);
MyStaticChild.Save(FileNamePath);
doSomeThing(MyStaticChild.Payload.Attribute);
doSomething(MyStaticChild.AttributeBase);
doSomeThing(MyStaticChild.AttributeChild);
// ...

Người tạo ra đứa trẻ tĩnh không cần phải suy nghĩ về quá trình tuần tự hóa miễn là họ hiểu bất kỳ giới hạn nào có thể được đặt trên công cụ tuần tự hóa của nền tảng hoặc môi trường.

Statics (singletons và các dạng khác của 'toàn cầu') thường xuất hiện xung quanh bộ lưu trữ cấu hình. Kế thừa tĩnh sẽ cho phép loại phân bổ trách nhiệm này được thể hiện rõ ràng trong cú pháp để phù hợp với cấu trúc phân cấp. Mặc dù, như tôi đã chỉ ra, có rất nhiều tiềm năng cho sự mơ hồ lớn nếu các khái niệm kế thừa tĩnh cơ bản được thực hiện.

Tôi tin rằng sự lựa chọn thiết kế phù hợp sẽ là cho phép kế thừa tĩnh với những hạn chế cụ thể:

  1. Không ghi đè bất cứ điều gì. Đứa trẻ không thể thay thế các thuộc tính cơ sở, trường hoặc phương thức, ... Quá tải sẽ ổn, miễn là có sự khác biệt về chữ ký cho phép trình biên dịch sắp xếp con so với cơ sở.
  2. Chỉ cho phép các cơ sở tĩnh chung, bạn không thể kế thừa từ một cơ sở tĩnh không chung chung.

Bạn vẫn có thể thay đổi cùng một cửa hàng thông qua một tài liệu tham khảo chung MyStaticBase<ChildPayload>.SomeBaseField. Nhưng bạn sẽ không được khuyến khích vì loại chung sẽ phải được chỉ định. Trong khi tham chiếu con sẽ sạch hơn : MyStaticChild.SomeBaseField.

Tôi không phải là người viết trình biên dịch nên tôi không chắc mình có thiếu điều gì về những khó khăn khi thực hiện những hạn chế này trong trình biên dịch hay không. Điều đó nói rằng, tôi là một người tin tưởng mạnh mẽ rằng có một không gian thông tin cần có sự kế thừa tĩnh hạn chế và câu trả lời cơ bản là bạn không thể vì một lựa chọn thiết kế kém (hoặc quá đơn giản).


0

Các lớp tĩnh và các thành viên lớp được sử dụng để tạo dữ liệu và các hàm có thể được truy cập mà không cần tạo một thể hiện của lớp. Các thành viên lớp tĩnh có thể được sử dụng để phân tách dữ liệu và hành vi độc lập với bất kỳ danh tính đối tượng nào: dữ liệu và chức năng không thay đổi bất kể điều gì xảy ra với đối tượng. Các lớp tĩnh có thể được sử dụng khi không có dữ liệu hoặc hành vi trong lớp phụ thuộc vào danh tính đối tượng.

Một lớp có thể được khai báo tĩnh, chỉ ra rằng nó chỉ chứa các thành viên tĩnh. Không thể sử dụng từ khóa mới để tạo các thể hiện của một lớp tĩnh. Các lớp tĩnh được tải tự động bởi thời gian chạy ngôn ngữ chung .NET Framework (CLR) khi chương trình hoặc không gian tên chứa lớp được tải.

Sử dụng một lớp tĩnh để chứa các phương thức không được liên kết với một đối tượng cụ thể. Ví dụ, yêu cầu chung là tạo một tập hợp các phương thức không hành động trên dữ liệu cá thể và không được liên kết với một đối tượng cụ thể trong mã của bạn. Bạn có thể sử dụng một lớp tĩnh để giữ các phương thức đó.

Sau đây là các tính năng chính của một lớp tĩnh:

  1. Chúng chỉ chứa các thành viên tĩnh.

  2. Họ không thể được khởi tạo.

  3. Chúng được niêm phong.

  4. Chúng không thể chứa Trình xây dựng sơ thẩm (Hướng dẫn lập trình C #).

Do đó, việc tạo một lớp tĩnh về cơ bản giống như tạo một lớp chỉ chứa các thành viên tĩnh và một hàm tạo riêng. Một constructor riêng ngăn chặn lớp được khởi tạo.

Ưu điểm của việc sử dụng một lớp tĩnh là trình biên dịch có thể kiểm tra để đảm bảo rằng không có thành viên cá thể nào được vô tình thêm vào. Trình biên dịch sẽ đảm bảo rằng các thể hiện của lớp này không thể được tạo.

Các lớp tĩnh được niêm phong và do đó không thể được kế thừa. Họ không thể kế thừa từ bất kỳ lớp nào ngoại trừ Object. Các lớp tĩnh không thể chứa một hàm tạo; tuy nhiên, họ có thể có một hàm tạo tĩnh. Để biết thêm thông tin, hãy xem Trình xây dựng tĩnh (Hướng dẫn lập trình C #).


-1

Khi chúng ta tạo một lớp tĩnh chỉ chứa các thành viên tĩnh và một hàm tạo riêng. Lý do duy nhất là hàm tạo tĩnh ngăn không cho lớp được tạo ngay lập tức vì chúng ta không thể kế thừa một lớp tĩnh. Cách duy nhất để truy cập vào thành viên của lớp tĩnh bằng cách sử dụng tên lớp của chính nó. Thử kế thừa một lớp tĩnh không phải là một ý tưởng tốt.

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.