Mengapa saya tidak dapat mewarisi kelas statis?

224

Saya memiliki beberapa kelas yang tidak benar-benar membutuhkan keadaan apa pun. Dari sudut pandang organisasi, saya ingin menempatkan mereka dalam hierarki.

Tapi sepertinya saya tidak bisa mendeklarasikan pewarisan untuk kelas statis.

Sesuatu seperti itu:

public static class Base
{
}

public static class Inherited : Base
{
}

tidak akan berfungsi.

Mengapa para perancang bahasa menutup kemungkinan itu?

Pengguna
sumber
Kemungkinan rangkap dari Bagaimana kelas statis dapat berasal dari suatu objek?
Muslim Ben Dhaou

Jawaban:

174

Kutipan dari sini :

Ini sebenarnya berdasarkan desain. Tampaknya tidak ada alasan yang baik untuk mewarisi kelas statis. Ini memiliki anggota statis publik yang selalu dapat Anda akses melalui nama kelas itu sendiri. Satu-satunya alasan yang saya lihat untuk mewarisi hal-hal statis adalah alasan yang buruk, seperti menyimpan beberapa karakter pengetikan.

Mungkin ada alasan untuk mempertimbangkan mekanisme untuk membawa anggota statis secara langsung ke dalam cakupan (dan kami sebenarnya akan mempertimbangkan ini setelah siklus produk Orcas), tetapi pewarisan kelas statis bukanlah cara yang harus ditempuh: Ini adalah mekanisme yang salah untuk digunakan, dan bekerja hanya untuk anggota statis yang kebetulan berada di kelas statis.

(Mads Torgersen, C # Bahasa PM)

Pendapat lain dari channel9

Warisan dalam .NET hanya berfungsi atas dasar instance. Metode statis didefinisikan pada level tipe bukan pada level instance. Itulah sebabnya penggantian utama tidak berfungsi dengan metode / properti / acara statis ...

Metode statis hanya disimpan satu kali dalam memori. Tidak ada tabel virtual dll yang dibuat untuk mereka.

Jika Anda memanggil metode instance dalam .NET, Anda selalu memberikannya instance saat ini. Ini disembunyikan oleh .NET runtime, tetapi itu terjadi. Setiap instance instance memiliki argumen pertama sebagai penunjuk (referensi) ke objek tempat metode dijalankan. Ini tidak terjadi dengan metode statis (seperti yang didefinisikan pada level tipe). Bagaimana seharusnya kompiler memutuskan untuk memilih metode yang akan dipanggil?

(littleguru)

Dan sebagai ide yang berharga, littleguru memiliki "solusi" parsial untuk masalah ini: pola Singleton .

boj
sumber
92
Kurang imajinasi di pihak Torgersen, sungguh. Saya punya alasan yang cukup bagus untuk ingin melakukan ini :)
Thorarin
10
Bagaimana dengan ini? Anda memiliki dll yang bukan open source / Anda tidak memiliki akses ke sumbernya, katakanlah NUnit. Anda ingin memperluas kelas Assert untuk memiliki metode seperti Throws <> (Action a) (ya, itu ada di sana juga, tetapi pada satu titik tidak.) Anda dapat menambahkan proyek Anda sebuah Assert: NUnit.Framework.Assert kelas, dan kemudian tambahkan metode Throws. Masalah terpecahkan. Ini lebih bersih untuk digunakan dalam kode juga ... daripada harus datang dengan nama kelas lain dan ingat nama kelas itu, cukup ketikkan Assert. dan lihat metode yang tersedia.
user420667
4
@KonradMorawski Tidak mungkin untuk menulis metode ekstensi untuk kelas statis. stackoverflow.com/questions/249222/…
Martin Braun
2
@modiX Tentu saja. Anda salah paham di sini. Yang saya maksudkan adalah bahwa fungsionalitas yang diperlukan dalam skenario yang dijelaskan oleh user420667 dapat disediakan ( di masa depan ) dengan sesedikit memungkinkan metode ekstensi untuk kelas statis. Tidak diperlukan pewarisan statis. Itu sebabnya skenarionya tidak mengejutkan saya sebagai alasan yang sangat baik untuk memperkenalkan warisan statis. Jika C # harus dimodifikasi dalam aspek ini, mengapa pergi untuk mendesain ulang besar ketika yang kecil akan sama baiknya dalam praktek.
Konrad Morawski
5
@Pelajar Itu tidak terlalu adil. Di .Net semuanya berasal dari object, dan semua orang diharapkan mengetahuinya. Jadi kelas statis selalu diwarisi dari object, apakah Anda menentukannya secara eksplisit atau tidak.
RenniePet
71

Alasan utama bahwa Anda tidak dapat mewarisi kelas statis adalah karena mereka abstrak dan disegel (ini juga mencegah setiap instance dari mereka dibuat).

Jadi ini:

static class Foo { }

kompilasi untuk IL ini:

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }
Andrew Hare
sumber
17
Anda mengatakan bahwa statis diimplementasikan sebagai abstrak + disegel. Dia ingin tahu mengapa ini dilakukan. Mengapa itu disegel?
Lucas
22
Ini tidak menjawab pertanyaan itu semua; itu hanya menyatakan kembali masalah yang dia tanyakan.
Igby Largeman
28
Nah, OP seharusnya bertanya "Mengapa kelas statis disegel", bukan "Mengapa saya tidak bisa mewarisi dari kelas statis?", Jawaban yang tentu saja "karena mereka disegel" . Jawaban ini mendapatkan suara saya.
Alex Budovski
6
terima kasih telah berbagi bahwa kelas statis secara otomatis dikompilasi sebagai kelas tersegel - saya bertanya-tanya bagaimana / kapan itu terjadi!
Mark
23
@AlexBudovski: Jawaban ini benar tetapi sama tidak bergunanya dengan mengatakan kepada orang yang hilang "Anda ada di depan saya" ketika mereka bertanya kepada Anda "di mana aku".
DeepSpace101
24

Pikirkan seperti ini: Anda mengakses anggota statis melalui nama tipe, seperti ini:

MyStaticType.MyStaticMember();

Jika Anda mewarisi dari kelas itu, Anda harus mengaksesnya melalui nama tipe baru:

MyNewType.MyStaticMember();

Dengan demikian, item baru tidak memiliki hubungan dengan aslinya ketika digunakan dalam kode. Tidak akan ada cara untuk mengambil keuntungan dari hubungan pewarisan untuk hal-hal seperti polimorfisme.

Mungkin Anda berpikir Anda hanya ingin memperpanjang beberapa item di kelas asli. Dalam hal itu, tidak ada yang mencegah Anda dari hanya menggunakan anggota asli dalam tipe yang sama sekali baru.

Mungkin Anda ingin menambahkan metode ke tipe statis yang ada. Anda dapat melakukannya melalui metode ekstensi.

Mungkin Anda ingin dapat memberikan statis Typeke fungsi saat runtime dan memanggil metode pada tipe itu, tanpa tahu persis apa yang dilakukan metode tersebut. Dalam hal ini, Anda dapat menggunakan Antarmuka.

Jadi, pada akhirnya Anda tidak benar-benar mendapatkan apa pun dari mewarisi kelas statis.

Joel Coehoorn
sumber
5
Anda tidak dapat menambahkan metode ke tipe statis yang ada melalui metode ekstensi. Silakan lihat komentar saya pada jawaban yang diterima untuk contoh penggunaan yang diinginkan. (Pada dasarnya Anda hanya ingin mengubah nama yang Anda beri nama MyNewType sebagai MyStaticType, jadi MyStaticType: OldProject.MyStaticType)
user420667
2
Orang dapat mendefinisikan hubungan pewarisan dengan tipe statis dan anggota statis virtual sedemikian rupa sehingga SomeClass<T> where T:Foodapat mengakses anggota Foo, dan jika Tkelas yang mengalahkan beberapa anggota statis virtual Foo, kelas generik akan menggunakan penggantian tersebut. Bahkan mungkin untuk mendefinisikan konvensi melalui mana bahasa dapat melakukannya dengan cara yang kompatibel dengan CLR saat ini (misalnya kelas dengan anggota tersebut harus mendefinisikan kelas non-statis yang dilindungi yang berisi anggota instance seperti itu, bersama dengan bidang statis memegang instance dari tipe itu).
supercat
Saya menemukan diri saya dengan sebuah kasus di mana saya percaya mewarisi kelas statis adalah hal yang benar untuk dilakukan, walaupun jelas Anda tetap dapat mengaksesnya. Saya memiliki kelas statis untuk mengirim aliran data mentah ke printer (untuk berbicara dengan printer label industri). Ini memiliki banyak deklarasi API windows. Sekarang saya menemukan diri saya menulis kelas lain untuk mengacaukan printer yang membutuhkan panggilan API yang sama. Menggabungkan? Tidak baik. Nyatakan dua kali? Jelek. Mewarisi? Bagus, tapi tidak diizinkan.
Loren Pechtel
3

Apa yang ingin Anda capai dengan menggunakan hierarki kelas dapat dicapai hanya melalui namespacing. Jadi bahasa yang mendukung namespapces (seperti C #) tidak akan menggunakan implementasi hirarki kelas dari kelas statis. Karena Anda tidak dapat membuat instance dari salah satu kelas, yang Anda butuhkan adalah organisasi hirarki definisi kelas yang dapat Anda peroleh melalui penggunaan ruang nama

Amit
sumber
Saya memiliki pemuat generik yang menerima kelas sebagai tipe. Kelas itu memiliki 5 metode pembantu dan 2 metode yang mengembalikan sebuah string (nama dan deskripsi). Saya mungkin salah mengambil (saya pikir begitu), tetapi satu-satunya solusi yang saya temukan adalah untuk membuat instance kelas di generik loader, untuk mengakses metode ... meh.
ANeves
Alternatifnya adalah kamus raksasa, kurasa. Juga, ketika Anda mengatakan "apa yang ingin Anda capai", Anda seharusnya mengatakan "apa yang ingin Anda capai" atau sesuatu yang serupa.
ANeves
3

Anda dapat menggunakan komposisi sebagai gantinya ... ini akan memungkinkan Anda untuk mengakses objek kelas dari tipe statis. Tetapi masih tidak bisa mengimplementasikan antarmuka atau kelas abstrak

Yogesh Karekar
sumber
2

Meskipun Anda dapat mengakses anggota statis "bawaan" melalui nama kelas bawaan, anggota statis tidak benar-benar diwariskan. Ini sebagian mengapa mereka tidak bisa virtual atau abstrak dan tidak bisa diganti. Dalam contoh Anda, jika Anda mendeklarasikan Base.Method (), kompiler akan memetakan panggilan ke Inherited.Method () kembali ke Base.Method (). Anda mungkin juga memanggil Base.Method () secara eksplisit. Anda dapat menulis tes kecil dan lihat hasilnya dengan Reflector.

Jadi ... jika Anda tidak dapat mewarisi anggota statis, dan jika kelas statis hanya dapat berisi anggota statis, apa gunanya mewarisi kelas statis?

Lucas
sumber
1

Hmmm ... apakah akan jauh berbeda jika Anda hanya memiliki kelas non-statis yang diisi dengan metode statis ..?

CookieOfFortune
sumber
2
Itulah yang tersisa untuk saya. Saya melakukannya dengan cara ini. Dan saya tidak suka itu.
Pengguna
Saya telah melakukannya dengan cara ini juga, tetapi untuk beberapa alasan itu tidak terasa estetika ketika Anda tahu Anda tidak akan pernah instantiate objek. Saya selalu sedih tentang detail seperti ini, meskipun faktanya tidak ada biaya material untuk itu.
gdbj
Di kelas non-statis diisi dengan metode statis Anda tidak dapat memasukkan metode ekstensi :)
Jakub Szułakiewicz
1

Solusi yang dapat Anda lakukan adalah tidak menggunakan kelas statis tetapi sembunyikan konstruktor sehingga anggota kelas statis adalah satu-satunya hal yang dapat diakses di luar kelas. Hasilnya adalah kelas "statis" bawaan pada dasarnya:

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>();

Sayangnya karena keterbatasan bahasa "by-design" yang tidak dapat kami lakukan:

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

public static class TestClass : TestClass<double>
{
}
Serigala Terfokus
sumber
1

Anda dapat melakukan sesuatu yang akan tampak seperti warisan statis.

Ini triknya:

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

Maka Anda dapat melakukan ini:

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

    public void MethodA()
    {
    }
}

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

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

The Inheritedkelas tidak statis itu sendiri, tapi kami tidak memungkinkan untuk menciptakannya. Ini sebenarnya telah mewarisi konstruktor statis yang dibangun Base, dan semua properti dan metode Basetersedia sebagai statis. Sekarang satu-satunya hal yang harus dilakukan adalah membuat pembungkus statis untuk setiap metode dan properti yang perlu Anda paparkan ke konteks statis Anda.

Ada kelemahan seperti kebutuhan untuk pembuatan manual metode pembungkus statis dan newkata kunci. Tetapi pendekatan ini membantu mendukung sesuatu yang benar-benar mirip dengan pewarisan statis.

PS Kami menggunakan ini untuk membuat kueri yang dikompilasi, dan ini sebenarnya dapat diganti dengan ConcurrentDictionary, tetapi bidang read-only statis dengan keamanan utasnya cukup baik.

Konard
sumber
1

Jawaban saya: pilihan desain yang buruk. ;-)

Ini adalah debat menarik yang berfokus pada dampak sintaksis. Inti dari argumen, dalam pandangan saya, adalah bahwa keputusan desain mengarah ke kelas statis tersegel. Fokus pada transparansi nama-nama kelas statis yang muncul di tingkat atas alih-alih bersembunyi ('membingungkan') di balik nama anak? Orang dapat membayangkan implementasi bahasa yang dapat mengakses pangkalan atau anak secara langsung, membingungkan.

Contoh pseudo, dengan asumsi pewarisan statis didefinisikan dalam beberapa cara.

public static class MyStaticBase
{
    SomeType AttributeBase;
}

public static class MyStaticChild : MyStaticBase
{
    SomeType AttributeChild;
}

akan mengarah ke:

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

yang dapat (akan?) memengaruhi penyimpanan yang sama dengan

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

Sangat membingungkan!

Tapi tunggu! Bagaimana kompiler menangani MyStaticBase dan MyStaticChild memiliki tanda tangan yang sama di keduanya? Jika anak menimpa daripada contoh saya di atas TIDAK akan mengubah penyimpanan yang sama, mungkin? Hal ini menyebabkan lebih banyak kebingungan.

Saya percaya ada pembenaran ruang informasi yang kuat untuk pewarisan statis terbatas. Lebih lanjut tentang batas tak lama. Kodesemu ini menunjukkan nilainya:

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(){/*...*/};
}

Maka Anda mendapatkan:

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

Penggunaannya terlihat seperti:

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

Orang yang menciptakan anak statis tidak perlu memikirkan proses serialisasi selama mereka memahami batasan apa pun yang mungkin ditempatkan pada mesin serialisasi platform atau lingkungan.

Statika (lajang dan bentuk 'global' lainnya) sering muncul di sekitar penyimpanan konfigurasi. Warisan statis akan memungkinkan alokasi tanggung jawab semacam ini diwakili dengan rapi dalam sintaksis agar sesuai dengan hierarki konfigurasi. Meskipun, seperti yang saya tunjukkan, ada banyak potensi ambiguitas masif jika konsep pewarisan statis dasar diterapkan.

Saya percaya pilihan desain yang tepat adalah untuk memungkinkan warisan statis dengan batasan spesifik:

  1. Tidak ada yang menimpa apa pun. Anak tidak dapat mengganti atribut dasar, bidang, atau metode, ... Kelebihan beban harus ok, asalkan ada perbedaan dalam tanda tangan yang memungkinkan kompiler untuk memilah-milah basis anak.
  2. Hanya izinkan basis statis generik, Anda tidak dapat mewarisi dari basis statis non-generik.

Anda masih dapat mengubah toko yang sama melalui referensi umum MyStaticBase<ChildPayload>.SomeBaseField. Tetapi Anda akan berkecil hati karena jenis generik harus ditentukan. Sedangkan referensi anak akan menjadi lebih bersih: MyStaticChild.SomeBaseField.

Saya bukan penulis kompiler jadi saya tidak yakin apakah saya kehilangan sesuatu tentang kesulitan menerapkan batasan ini dalam kompiler. Yang mengatakan, saya sangat percaya bahwa ada kebutuhan ruang informasi untuk pewarisan statis terbatas dan jawaban dasarnya adalah Anda tidak bisa karena pilihan desain yang buruk (atau terlalu sederhana).

Dale Strickler
sumber
0

Kelas statis dan anggota kelas digunakan untuk membuat data dan fungsi yang dapat diakses tanpa membuat turunan kelas. Anggota kelas statis dapat digunakan untuk memisahkan data dan perilaku yang independen dari identitas objek apa pun: data dan fungsi tidak berubah terlepas dari apa yang terjadi pada objek. Kelas statis dapat digunakan ketika tidak ada data atau perilaku di kelas yang tergantung pada identitas objek.

Kelas dapat dideklarasikan statis, yang menunjukkan bahwa itu hanya berisi anggota statis. Tidak mungkin menggunakan kata kunci baru untuk membuat instance kelas statis. Kelas statis dimuat secara otomatis oleh .NET Framework common language runtime (CLR) ketika program atau namespace yang berisi kelas dimuat.

Gunakan kelas statis untuk memuat metode yang tidak terkait dengan objek tertentu. Misalnya, itu adalah persyaratan umum untuk membuat seperangkat metode yang tidak bertindak atas data contoh dan tidak terkait dengan objek tertentu dalam kode Anda. Anda bisa menggunakan kelas statis untuk menampung metode-metode itu.

Berikut ini adalah fitur utama dari kelas statis:

  1. Mereka hanya berisi anggota statis.

  2. Mereka tidak bisa dipakai.

  3. Mereka dimeteraikan.

  4. Mereka tidak dapat mengandung Instance Constructors (Panduan Pemrograman C #).

Karena itu, membuat kelas statis pada dasarnya sama dengan membuat kelas yang hanya berisi anggota statis dan konstruktor pribadi. Konstruktor pribadi mencegah kelas agar tidak dipakai.

Keuntungan menggunakan kelas statis adalah bahwa kompiler dapat memeriksa untuk memastikan bahwa tidak ada anggota instance ditambahkan secara tidak sengaja. Kompiler akan menjamin bahwa instance dari kelas ini tidak dapat dibuat.

Kelas statis disegel dan karenanya tidak dapat diwarisi. Mereka tidak dapat mewarisi dari kelas apa pun kecuali Object. Kelas statis tidak dapat mengandung konstruktor instance; Namun, mereka dapat memiliki konstruktor statis. Untuk informasi lebih lanjut, lihat Konstruktor Statis (Panduan Pemrograman C #).

Daerah
sumber
-1

Ketika kita membuat kelas statis yang hanya berisi anggota statis dan konstruktor pribadi. Satu-satunya alasan adalah bahwa konstruktor statis mencegah kelas dari yang dipakai untuk itu kita tidak bisa mewarisi kelas statis. Satu-satunya cara untuk mengakses anggota kelas statis dengan menggunakan nama kelas itu sendiri. Mencoba mewarisi kelas statis bukanlah ide yang baik.

Soumen Chakraborty
sumber