Perché non posso avere metodi statici astratti in C #?


182

Ultimamente ho lavorato con i fornitori e mi sono imbattuto in una situazione interessante in cui volevo avere una classe astratta con un metodo statico astratto. Ho letto alcuni post sull'argomento, e in un certo senso aveva senso, ma c'è una bella spiegazione chiara?


3
Si prega di lasciarli aperti per consentire futuri miglioramenti.
Mark Biek,

6
Penso che la domanda sia arrivare al fatto che C # ha bisogno di un'altra parola chiave, proprio per questo tipo di situazione. Si desidera un metodo il cui valore restituito dipende solo dal tipo su cui viene chiamato. Non puoi chiamarlo "statico" se detto tipo è sconosciuto. Ma una volta che il tipo diventa noto, diventerà statico. "Statica irrisolta" è l'idea: non è ancora statica, ma una volta che conosciamo il tipo di ricezione, lo sarà. Questo è un concetto perfettamente valido, motivo per cui i programmatori continuano a chiederlo. Ma non si adattava perfettamente al modo in cui i designer pensavano al linguaggio.
William Jockusch,

@WilliamJockusch cosa significa ricevere tipo? Se chiamo BaseClass.StaticMethod (), allora BaseClass è l'unico tipo che può usare per prendere la decisione. Ma a questo livello è astratto, quindi il metodo non può essere risolto. Se invece si chiama bene DerivedClass.StaticMethod, la classe base è irrilevante.
Martin Capodici,

Nella classe base, il metodo è irrisolto e non è possibile utilizzarlo. È necessario un tipo derivato o un oggetto (che a sua volta avrebbe un tipo derivato). Dovresti essere in grado di chiamare baseClassObject.Method () o DerivedClass.Method (). Non puoi chiamare BaseClass.Method () perché non ti dà il tipo.
William Jockusch,

Risposte:


157

I metodi statici non sono istanziati in quanto tali, sono disponibili solo senza un riferimento all'oggetto.

Una chiamata a un metodo statico viene effettuata tramite il nome della classe, non tramite un riferimento a un oggetto, e il codice IL (Intermediate Language) per chiamarlo chiamerà il metodo astratto attraverso il nome della classe che lo ha definito, non necessariamente il nome di la classe che hai usato.

Lasciami mostrare un esempio.

Con il seguente codice:

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

public class B : A
{
}

Se chiami B.Test, in questo modo:

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

Quindi il codice effettivo all'interno del metodo Main è il seguente:

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

Come puoi vedere, la chiamata viene effettuata su A.Test, perché è stata la classe A a definirla, e non su B.Test, anche se puoi scrivere il codice in quel modo.

Se avessi tipi di classe , come in Delphi, in cui puoi creare una variabile riferendosi a un tipo e non a un oggetto, avresti più uso di metodi statici virtuali e quindi astratti (e anche di costruttori), ma non sono disponibili e pertanto le chiamate statiche non sono virtuali in .NET.

Mi rendo conto che i progettisti di IL potrebbero consentire la compilazione del codice per chiamare B.Test e risolvere la chiamata in fase di esecuzione, ma non sarebbe comunque virtuale, poiché dovresti comunque scrivere un qualche tipo di nome di classe lì.

I metodi virtuali, e quindi quelli astratti, sono utili solo quando si utilizza una variabile che, in fase di esecuzione, può contenere molti tipi diversi di oggetti, e quindi si desidera chiamare il metodo giusto per l'oggetto corrente che si ha nella variabile. Con i metodi statici devi comunque passare attraverso un nome di classe, quindi il metodo esatto da chiamare è noto al momento della compilazione perché non può e non cambierà.

Pertanto, i metodi statici virtuali / astratti non sono disponibili in .NET.


4
In combinazione con il modo in cui il sovraccarico dell'operatore viene eseguito in C #, questo purtroppo elimina la possibilità di richiedere sottoclassi per fornire un'implementazione per un determinato sovraccarico dell'operatore.
Chris Moschini,

23
Non trovo questa risposta terribilmente utile in quanto la definizione di Test()è Apiuttosto che essere astratta e potenzialmente definita in B. \

5
I parametri di tipo generico si comportano efficacemente come variabili di "tipo" non persistenti e metodi statici virtuali potrebbero essere utili in tale contesto. Ad esempio, se uno avesse un Cartipo con un CreateFromDescriptionmetodo factory statico virtuale , allora il codice che accettava un Cartipo generico vincolato Tpotrebbe chiamare T.CreateFromDescriptionper produrre una macchina di tipo T. Tale costrutto potrebbe essere supportato abbastanza bene all'interno del CLR se ogni tipo che definiva tale metodo contenesse un'istanza singleton statica di un generico di classe nidificata che conteneva i metodi "statici" virtuali.
supercat

45

I metodi statici non possono essere ereditati o ignorati ed è per questo che non possono essere astratti. Poiché i metodi statici sono definiti sul tipo, non sull'istanza, di una classe, devono essere chiamati esplicitamente su quel tipo. Pertanto, quando si desidera chiamare un metodo su una classe figlio, è necessario utilizzare il suo nome per chiamarlo. Ciò rende l'eredità irrilevante.

Supponiamo che potresti, per un momento, ereditare metodi statici. Immagina questo scenario:

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

Se chiami Base.GetNumber (), quale metodo verrebbe chiamato? Quale valore restituito? È abbastanza facile vedere che senza creare istanze di oggetti, l'eredità è piuttosto difficile. I metodi astratti senza ereditarietà sono solo metodi che non hanno un corpo, quindi non possono essere chiamati.


33
Dato il tuo scenario, direi Base.GetNumber () restituirà 5; Child1.GetNumber () restituisce 1; Child2.GetNumber () restituisce 2; Puoi dimostrarmi che ho torto, per aiutarmi a capire il tuo ragionamento? Grazie
Luis Filipe,

La faccia che pensi Base.GetNumber () restituisce 5, significa che hai già capito cosa sta succedendo. Restituendo il valore di base, non è in corso alcuna eredità.
David Wengier,

60
Perché nel mondo Base.GetNumber () restituirebbe nient'altro che 5? È un metodo nella classe base - c'è solo 1 opzione lì.
Artem Russakovskii,

4
@ArtemRussakovskii: Supponiamo che uno abbia avuto int DoSomething<T>() where T:Base {return T.GetNumber();}. Sembrerebbe utile se DoSomething<Base>()potesse restituirne cinque, mentre DoSomething<Child2>()ne restituirebbe due. Tale abilità sarebbe utile non solo per esempi di giocattoli, ma anche per qualcosa del genere class Car {public static virtual Car Build(PurchaseOrder PO);}, in cui ogni classe derivante Cardovrebbe definire un metodo che potrebbe costruire un'istanza dato un ordine di acquisto.
supercat,

4
Esiste esattamente lo stesso "problema" con l'ereditarietà non statica.
Ark-kun,

18

Un altro intervistato (McDowell) ha affermato che il polimorfismo funziona solo per istanze di oggetti. Questo dovrebbe essere qualificato; ci sono lingue che trattano le classi come istanze di un tipo "Class" o "Metaclass". Questi linguaggi supportano il polimorfismo sia per i metodi di istanza che per quelli di classe (statici).

C #, come Java e C ++ prima, non è un tale linguaggio; la staticparola chiave viene utilizzata esplicitamente per indicare che il metodo è associato staticamente anziché dinamico / virtuale.


9

Ecco una situazione in cui c'è sicuramente bisogno di ereditarietà per campi e metodi statici:

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
No, c'è solo un'istanza delle 'gambe' dell'array. L'output è non deterministico in quanto non si conosce l'ordine con cui verranno chiamati i costruttori statici (in realtà non esiste alcuna garanzia che il costruttore statico di classe base venga chiamato affatto). "Bisogno" è un termine abbastanza assoluto in cui "desiderio" è probabilmente più preciso.
Sam,

legsdovrebbe essere una proprietà astratta statica.
Little Endian,

8

Per aggiungere alle spiegazioni precedenti, le chiamate al metodo statico sono legate a un metodo specifico in fase di compilazione , il che esclude piuttosto il comportamento polimorfico.


C # è tipizzato staticamente; le chiamate ai metodi polimorfici sono anche legate al momento della compilazione, come ho capito, vale a dire che il CLR non viene lasciato per risolvere quale metodo chiamare durante il runtime.
Adam Tolley,

Quindi, come pensi esattamente che il polimorfismo funzioni sul CLR? La tua spiegazione ha escluso l'invio di metodi virtuali.
Rytmis,

Non è un commento così utile come potrebbe essere. Ho invitato (con "come ho capito") un discorso utile, penso che forse potresti fornire un po 'più di contenuto - visto che le persone vengono qui in cerca di risposte e non di insulti. Anche se, a quanto pare, posso essere colpevole della stessa cosa - intendevo davvero il commento sopra come una domanda: C # non valuta queste cose in fase di compilazione?
Adam Tolley,

Mi scuso, non intendevo un insulto (anche se ammetto di rispondere in modo un po 'scattante ;-). Il punto della mia domanda era, se hai queste classi: class Base {metodo virtuale vuoto pubblico (); } class Derived: Base {public override void Method (); } e scrivere così: Base instance = new Derived (); instance.Method (); le informazioni sul tipo di tempo di compilazione sul sito di chiamata sono che abbiamo un'istanza di Base, quando l'istanza effettiva è una derivata. Quindi il compilatore non può risolvere il metodo esatto da chiamare. Invece emette un'istruzione IL "callvirt" che dice al runtime di spedire ..
Rytmis,

1
Grazie amico, questo è informativo! Immagino di aver rimandato l'immersione nell'IL da abbastanza tempo, augurami buona fortuna.
Adam Tolley,

5

In realtà sovrascriviamo i metodi statici (in delphi), è un po 'brutto, ma funziona bene per le nostre esigenze.

Lo usiamo in modo che le classi possano avere un elenco dei loro oggetti disponibili senza l'istanza della classe, ad esempio, abbiamo un metodo che assomiglia a questo:

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

È brutto ma necessario, in questo modo possiamo creare un'istanza di ciò che è necessario, invece di avere tutte le classi istanziate solo per cercare gli oggetti disponibili.

Questo è stato un semplice esempio, ma l'applicazione stessa è un'applicazione client-server che ha tutte le classi disponibili in un solo server e più client diversi che potrebbero non aver bisogno di tutto ciò che il server ha e non avranno mai bisogno di un'istanza di oggetto.

Quindi è molto più facile da gestire rispetto ad avere un'applicazione server diversa per ogni client.

Spero che l'esempio sia stato chiaro.


0

I metodi astratti sono implicitamente virtuali. I metodi astratti richiedono un'istanza, ma i metodi statici non hanno un'istanza. Quindi, puoi avere un metodo statico in una classe astratta, semplicemente non può essere astratto statico (o statico astratto).


1
-1 i metodi virtuali non hanno bisogno di un'istanza, tranne che per progettazione. E in realtà non affronti la domanda, tanto quanto deviarla.
FallenAvatar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.