Mengapa saya tidak dapat memiliki metode statis abstrak di C #?

182

Saya telah bekerja dengan penyedia sedikit adil belakangan ini, dan saya menemukan situasi yang menarik di mana saya ingin memiliki kelas abstrak yang memiliki metode statis abstrak. Saya membaca beberapa posting tentang topik ini, dan itu masuk akal, tetapi apakah ada penjelasan yang jelas dan bagus?

lomaxx
sumber
3
Harap biarkan ini terbuka untuk memungkinkan peningkatan di masa mendatang.
Mark Biek
6
Saya pikir pertanyaannya adalah pada kenyataan bahwa C # membutuhkan kata kunci lain, untuk situasi seperti ini. Anda menginginkan metode yang nilai pengembaliannya hanya bergantung pada jenis yang dipanggil. Anda tidak dapat menyebutnya "statis" jika jenis kata tidak diketahui. Tetapi begitu jenisnya diketahui, itu akan menjadi statis. "Statis yang belum terselesaikan" adalah idenya - ini belum statis, tetapi begitu kita mengetahui tipe penerima, itu akan menjadi. Ini adalah konsep yang sangat baik, itulah sebabnya programmer terus memintanya. Tapi itu tidak cukup sesuai dengan cara para perancang berpikir tentang bahasa.
William Jockusch
@ WillyJockusch apa arti dari tipe penerima? Jika saya memanggil BaseClass.StaticMethod () maka BaseClass adalah satu-satunya jenis yang dapat digunakan untuk membuat keputusan. Tetapi pada level ini abstrak sehingga metodenya tidak bisa diselesaikan. Jika Anda sebaliknya memanggil DerivedClass.StaticMethod dengan baik maka kelas dasar tidak relevan.
Martin Capodici
Di kelas dasar, metode ini tidak terselesaikan, dan Anda tidak dapat menggunakannya. Anda memerlukan tipe turunan atau objek (yang pada gilirannya akan memiliki tipe turunan). Anda harus dapat memanggil baseClassObject.Method () atau DerivedClass.Method (). Anda tidak dapat memanggil BaseClass.Method () karena itu tidak memberi Anda jenisnya.
William Jockusch
Kemungkinan rangkap dari Bagaimana menerapkan properti statis virtual?
peterh

Jawaban:

157

Metode statis tidak dipakai seperti itu, mereka hanya tersedia tanpa referensi objek.

Panggilan ke metode statis dilakukan melalui nama kelas, bukan melalui referensi objek, dan kode Bahasa Menengah (IL) untuk memanggilnya akan memanggil metode abstrak melalui nama kelas yang mendefinisikannya, belum tentu nama kelas yang Anda gunakan.

Izinkan saya menunjukkan sebuah contoh.

Dengan kode berikut:

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

public class B : A
{
}

Jika Anda memanggil B.Test, seperti ini:

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

Maka kode aktual di dalam metode Utama adalah sebagai berikut:

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

Seperti yang Anda lihat, panggilan dibuat ke A.Test, karena itu adalah kelas A yang mendefinisikannya, dan bukan ke B.Test, meskipun Anda dapat menulis kode dengan cara itu.

Jika Anda memiliki tipe kelas , seperti di Delphi, di mana Anda dapat membuat variabel yang merujuk ke tipe dan bukan objek, Anda akan lebih banyak menggunakan metode virtual dan abstrak statis (dan juga konstruktor), tetapi mereka tidak tersedia dan dengan demikian panggilan statis non-virtual di .NET.

Saya menyadari bahwa desainer IL dapat memungkinkan kode dikompilasi untuk memanggil B.Test, dan menyelesaikan panggilan saat runtime, tetapi masih tidak virtual, karena Anda masih harus menulis semacam nama kelas di sana.

Metode virtual, dan dengan demikian yang abstrak, hanya berguna ketika Anda menggunakan variabel yang, pada saat runtime, dapat berisi berbagai jenis objek, dan karenanya Anda ingin memanggil metode yang tepat untuk objek saat ini yang Anda miliki dalam variabel. Dengan metode statis, Anda harus tetap menggunakan nama kelas, sehingga metode yang tepat untuk menelepon diketahui pada waktu kompilasi karena tidak dapat dan tidak akan berubah.

Dengan demikian, metode statis virtual / abstrak tidak tersedia di .NET.

Lasse V. Karlsen
sumber
4
Dikombinasikan dengan cara overloading operator dilakukan dalam C #, sayangnya ini menghilangkan kemungkinan membutuhkan subclass untuk menyediakan implementasi untuk overload operator tertentu.
Chris Moschini
23
Saya tidak menemukan jawaban ini sangat berguna karena definisi Test()dalam Adaripada abstrak dan berpotensi didefinisikan B. \
5
Parameter tipe generik berperilaku efektif sebagai variabel "tipe" yang tidak dapat bertahan, dan metode statis virtual dapat berguna dalam konteks tersebut. Misalnya, jika seseorang memiliki Cartipe dengan CreateFromDescriptionmetode pabrik statis virtual , maka kode yang menerima Cartipe generik yang dibatasi Tdapat memanggil T.CreateFromDescriptionuntuk menghasilkan mobil tipe T. Konstruk semacam itu dapat didukung dengan cukup baik di dalam CLR jika masing-masing tipe yang mendefinisikan metode semacam itu memiliki instance singleton statis dari generic class bersarang yang memiliki metode "statis" virtual.
supercat
45

Metode statis tidak bisa diwarisi atau diganti, dan itulah sebabnya mereka tidak bisa abstrak. Karena metode statis didefinisikan pada tipe, bukan instance, dari suatu kelas, mereka harus dipanggil secara eksplisit pada tipe itu. Jadi ketika Anda ingin memanggil metode pada kelas anak, Anda perlu menggunakan namanya untuk memanggilnya. Ini membuat warisan tidak relevan.

Asumsikan Anda dapat, untuk sesaat, mewarisi metode statis. Bayangkan skenario ini:

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

Jika Anda memanggil Base.GetNumber (), metode apa yang akan dipanggil? Nilai mana yang dikembalikan? Cukup mudah untuk melihat bahwa tanpa membuat instance objek, pewarisan agak sulit. Metode abstrak tanpa pewarisan hanyalah metode yang tidak memiliki tubuh, jadi tidak bisa disebut.

David Wengier
sumber
33
Dengan skenario Anda, saya akan mengatakan Base.GetNumber () akan mengembalikan 5; Child1.GetNumber () mengembalikan 1; Child2.GetNumber () mengembalikan 2; Bisakah Anda membuktikan saya salah, untuk membantu saya memahami alasan Anda? Terima kasih
Luis Filipe
Wajah yang Anda pikir Base.GetNumber () mengembalikan 5, berarti Anda sudah mengerti apa yang sedang terjadi. Dengan mengembalikan nilai dasar, tidak ada warisan yang terjadi.
David Wengier
60
Mengapa di dunia akan Base.GetNumber () mengembalikan apa pun selain 5? Ini adalah metode di kelas dasar - hanya ada 1 opsi di sana.
Artem Russakovskii
4
@ ArtemRussakovskii: Misalkan punya int DoSomething<T>() where T:Base {return T.GetNumber();}. Tampaknya bermanfaat jika DoSomething<Base>()dapat mengembalikan lima, sementara DoSomething<Child2>()akan mengembalikan dua. Kemampuan seperti itu tidak hanya berguna untuk contoh mainan, tetapi juga untuk sesuatu seperti class Car {public static virtual Car Build(PurchaseOrder PO);}, di mana setiap kelas yang berasal dari Carharus mendefinisikan metode yang dapat membangun contoh yang diberikan pesanan pembelian.
supercat
4
Ada "masalah" yang sama persis dengan warisan non-statis.
Ark-kun
18

Responden lain (McDowell) mengatakan bahwa polimorfisme hanya berfungsi untuk instance objek. Itu harus memenuhi syarat; ada bahasa yang memperlakukan kelas sebagai turunan dari tipe "Kelas" atau "Metaclass". Bahasa-bahasa ini memang mendukung polimorfisme untuk metode instance dan kelas (statis).

C #, seperti Java dan C ++ sebelumnya, bukan bahasa seperti itu; yang statickata kunci digunakan secara eksplisit untuk menunjukkan bahwa metode ini statis-terikat daripada dinamis / virtual.

Chris Hanson
sumber
9

Berikut adalah situasi di mana pasti ada kebutuhan untuk warisan untuk bidang dan metode statis:

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"?
pengguna275801
sumber
4
Tidak, hanya ada satu instance dari array 'legs'. Output tidak deterministik karena Anda tidak tahu urutan apa konstruktor statis akan dipanggil (sebenarnya tidak ada jaminan konstruktor statis kelas dasar akan dipanggil sama sekali). 'Kebutuhan' adalah istilah yang cukup absolut di mana 'keinginan' mungkin lebih akurat.
Sam
legsharus menjadi properti abstrak statis.
Little Endian
8

Untuk menambah penjelasan sebelumnya, panggilan metode statis terikat ke metode tertentu pada waktu kompilasi , yang lebih mengesampingkan perilaku polimorfik.

Rytmis
sumber
C # diketik secara statis; panggilan ke metode polimorfik juga terikat pada waktu kompilasi seperti yang saya pahami - artinya CLR tidak dibiarkan menyelesaikan metode yang akan dipanggil saat runtime.
Adam Tolley
Jadi, bagaimana tepatnya menurut Anda polimorfisme bekerja pada CLR? Penjelasan Anda hanya mengesampingkan pengiriman metode virtual.
Rytmis
Itu bukan komentar yang benar-benar berguna. Saya mengundang (dengan 'sebagaimana saya mengerti') wacana yang bermanfaat, berpikir mungkin Anda bisa memberikan sedikit lebih banyak konten - melihat ketika orang-orang datang ke sini mencari jawaban dan bukan menghina. Meskipun, sepertinya saya mungkin bersalah atas hal yang sama - saya benar-benar bermaksud komentar di atas sebagai pertanyaan: Bukankah C # mengevaluasi hal-hal ini pada waktu kompilasi?
Adam Tolley
Maaf, saya tidak bermaksud menghina (meskipun saya akui merespons dengan sedikit marah ;-). Inti dari pertanyaan saya adalah, jika Anda memiliki kelas-kelas ini: class Base {public virtual void Method (); } class Derived: Base {public override void Method (); } dan tulis dengan demikian: Contoh basis = turunan baru (); instance.Method (); informasi jenis waktu kompilasi di situs panggilan adalah bahwa kami memiliki instance dari Base, ketika instance sebenarnya adalah turunan. Jadi kompiler tidak dapat menyelesaikan metode yang tepat untuk memanggil. Alih-alih itu mengeluarkan instruksi IL "callvirt" yang memberitahu runtime untuk mengirim ..
Rytmis
1
Terima kasih kawan, itu informatif! Kira saya telah menunda menyelam ke IL cukup lama, doakan saya beruntung.
Adam Tolley
5

Kami benar-benar mengesampingkan metode statis (dalam delphi), ini agak jelek, tetapi berfungsi dengan baik untuk kebutuhan kita.

Kami menggunakannya sehingga kelas dapat memiliki daftar objek yang tersedia tanpa turunan kelas, misalnya, kami memiliki metode yang terlihat seperti ini:

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

Itu jelek tapi perlu, cara ini kita bisa instantiate hanya apa yang dibutuhkan, daripada memiliki semua kelas instantianted hanya untuk mencari objek yang tersedia.

Ini adalah contoh sederhana, tetapi aplikasi itu sendiri adalah aplikasi client-server yang memiliki semua kelas yang tersedia hanya dalam satu server, dan beberapa klien berbeda yang mungkin tidak memerlukan semua yang dimiliki server dan tidak akan pernah membutuhkan objek objek.

Jadi ini jauh lebih mudah untuk dipertahankan daripada memiliki satu aplikasi server yang berbeda untuk setiap klien.

Semoga contohnya jelas.

Fabio Gomes
sumber
0

Metode abstrak secara virtual implisit. Metode abstrak membutuhkan instance, tetapi metode statis tidak memiliki instance. Jadi, Anda dapat memiliki metode statis dalam kelas abstrak, itu tidak bisa abstrak statis (atau abstrak statis).

Pagi
sumber
1
-1 metode virtual tidak perlu instan, kecuali dengan desain. Dan Anda sebenarnya tidak menjawab pertanyaan itu, sama seperti membelokkannya.
FallenAvatar