Domanda

A differenza di Java, perché C # tratta i metodi come funzioni non virtuali per impostazione predefinita? È più probabile che sia un problema di prestazioni piuttosto che altri possibili risultati?

Mi viene in mente di leggere un paragrafo di Anders Hejlsberg sui numerosi vantaggi che l'architettura esistente sta mettendo in evidenza. Ma che dire degli effetti collaterali? È davvero un buon compromesso avere metodi non virtuali per impostazione predefinita?

È stato utile?

Soluzione

Le classi dovrebbero essere progettate affinché l'ereditarietà possa trarne vantaggio. Avere metodi virtual per impostazione predefinita significa che ogni funzione nella classe può essere scollegata e sostituita da un'altra, il che non è una buona cosa. Molte persone credono addirittura che le classi avrebbero dovuto essere sigillate per impostazione predefinita.

I metodi

?? virtuali possono anche avere una leggera implicazione sulle prestazioni. Questo non è probabilmente il motivo principale, tuttavia.

Altri suggerimenti

Sono sorpreso che qui ci sia un tale consenso sul fatto che non virtuale per impostazione predefinita è il modo giusto di fare le cose. Scenderò dall'altro lato - credo pragmatico - sul lato del recinto.

La maggior parte delle giustificazioni mi viene letta come la vecchia " Se ti diamo il potere potresti ferirti " discussione. Dai programmatori ?!

Mi sembra che il programmatore che non sapeva abbastanza (o che avesse abbastanza tempo) per progettare la propria libreria per eredità e / o estensibilità sia il programmatore che ha prodotto esattamente la libreria che probabilmente dovrò correggere o modificare - esattamente la libreria in cui sarebbe utile la possibilità di eseguire l'override.

Il numero di volte in cui ho dovuto scrivere un codice brutto e disperato per aggirare il problema (o abbandonare l'uso e implementare la mia soluzione alternativa) perché non posso ignorare molto, molto supera il numero di volte che abbia mai stato morso (ad es. in Java) sovrascrivendo il punto in cui il progettista potrebbe non aver pensato che potrei.

Non virtuale per impostazione predefinita mi rende la vita più difficile.

AGGIORNAMENTO: È stato sottolineato [abbastanza correttamente] che in realtà non ho risposto alla domanda. Quindi - e con scuse per essere piuttosto in ritardo ....

Volevo essere in grado di scrivere qualcosa di simile come "C # implementa metodi come non virtuali per impostazione predefinita perché è stata presa una decisione sbagliata che ha valutato i programmi più altamente dei programmatori". (Penso che potrebbe essere in qualche modo giustificato sulla base di alcune delle altre risposte a questa domanda, come le prestazioni (ottimizzazione prematura, chiunque?) O la garanzia del comportamento delle classi.)

Tuttavia, mi rendo conto che avrei solo dichiarato la mia opinione e non quella risposta definitiva che Stack Overflow desidera. Sicuramente, ho pensato, ai massimi livelli la risposta definitiva (ma inutile) è:

Sono non virtuali per impostazione predefinita perché i progettisti del linguaggio avevano una decisione da prendere ed è quello che hanno scelto.

Ora immagino che la ragione esatta per cui hanno preso quella decisione non lo faremo mai ... oh, aspetta! La trascrizione di una conversazione!

Quindi sembra che le risposte e i commenti qui sui pericoli delle API prevalenti e sulla necessità di progettare esplicitamente l'ereditarietà siano sulla buona strada ma mancano tutti di un importante aspetto temporale: la preoccupazione principale di Anders era di mantenere la classe o il contratto implicito dell'API tra versioni . E penso che in realtà sia più preoccupato di consentire alla piattaforma .Net / C # di cambiare sotto il codice piuttosto che preoccuparsi di cambiare il codice utente sulla piattaforma. (E il suo punto di vista "pragmatico" è l'esatto contrario del mio perché sta guardando dall'altra parte.)

(Ma non potevano semplicemente aver scelto virtual-by-default e poi inserito nella "base" del codice? Forse non è proprio lo stesso ... e Anders è chiaramente più intelligente di me, quindi ho intenzione di lasciare mentono.)

Perché è troppo facile dimenticare che un metodo può essere ignorato e non progettato per quello. C # ti fa pensare prima di renderlo virtuale. Penso che questa sia un'ottima decisione di progettazione. Alcune persone (come Jon Skeet) hanno persino affermato che le classi dovrebbero essere sigillate per impostazione predefinita.

Per riassumere ciò che hanno detto gli altri, ci sono alcuni motivi:

1- In C #, ci sono molte cose nella sintassi e nella semantica che provengono direttamente dal C ++. Il fatto che i metodi non virtuali per impostazione predefinita in C ++ abbiano influenzato C #.

2- Avere ogni metodo virtuale per impostazione predefinita è un problema di prestazioni perché ogni chiamata di metodo deve utilizzare la tabella virtuale dell'oggetto. Inoltre, ciò limita fortemente la capacità del compilatore Just-In-Time di incorporare metodi ed eseguire altri tipi di ottimizzazione.

3- Soprattutto, se i metodi non sono virtuali per impostazione predefinita, puoi garantire il comportamento delle tue classi. Quando sono virtuali per impostazione predefinita, come in Java, non puoi nemmeno garantire che un semplice metodo getter farà come previsto perché potrebbe essere ignorato per fare qualsiasi cosa in una classe derivata (ovviamente puoi e dovresti fare il metodo e / o la classe finale).

Ci si potrebbe chiedere, come ha detto Zifre, perché il linguaggio C # non è andato oltre e ha reso le classi sigillate per impostazione predefinita. Fa parte dell'intero dibattito sui problemi dell'ereditarietà dell'implementazione, che è un argomento molto interessante.

C # è influenzato da C ++ (e altro). C ++ non abilita l'invio dinamico (funzioni virtuali) per impostazione predefinita. Un argomento (buono?) Per questo è la domanda: " Quanto spesso implementate le classi che sono membri di una classe hiearchy? & Quot ;. Un altro motivo per evitare di abilitare l'invio dinamico per impostazione predefinita è l'impronta di memoria. Una classe senza un puntatore virtuale (vpointer) che punta a una tabella virtuale , è ovviamente più piccola della classe corrispondente con associazione in ritardo abilitata.

Il problema delle prestazioni non è così facile da dire "sì" o " no " a. Il motivo di ciò è la compilazione Just In Time (JIT) che è un'ottimizzazione del tempo di esecuzione in C #.

Un'altra domanda simile sulla " velocità delle chiamate virtuali. "

Il semplice motivo sono i costi di progettazione e manutenzione oltre ai costi di prestazione. Un metodo virtuale ha un costo aggiuntivo rispetto a un metodo non virtuale poiché il progettista della classe deve pianificare ciò che accade quando il metodo viene sostituito da un'altra classe. Ciò ha un grande impatto se ti aspetti che un particolare metodo aggiorni lo stato interno o abbia un comportamento particolare. Ora devi pianificare cosa succede quando una classe derivata cambia quel comportamento. È molto più difficile scrivere un codice affidabile in quella situazione.

Con un metodo non virtuale hai il controllo totale. Tutto ciò che va storto è colpa dell'autore originale. Il codice è molto più facile da ragionare.

Se tutti i metodi C # fossero virtuali, allora il vtbl sarebbe molto più grande.

Gli oggetti C # hanno metodi virtuali solo se nella classe sono definiti metodi virtuali. È vero che tutti gli oggetti hanno informazioni sul tipo che includono un equivalente vtbl, ma se non vengono definiti metodi virtuali, saranno presenti solo i metodi Object di base.

@Tom Hawtin: Probabilmente è più preciso affermare che C ++, C # e Java appartengono tutti alla famiglia di lingue C :)

Proveniente da un background perl penso che C # abbia sigillato il destino di ogni sviluppatore che avrebbe potuto voler estendere e modificare il comportamento di una classe base 'attraverso un metodo non virtuale senza costringere tutti gli utenti della nuova classe a essere consapevoli del potenziale dietro i dettagli della scena.

Considera il metodo Aggiungi della classe Elenco. Che cosa succede se uno sviluppatore desidera aggiornare uno dei numerosi database potenziali ogni volta che viene aggiunto un determinato elenco? Se "Aggiungi" fosse stato virtuale per impostazione predefinita, lo sviluppatore avrebbe potuto sviluppare una classe "BackedList" che sostituiva il metodo "Aggiungi" senza forzare tutto il codice client a sapere che era un "BackedList" anziché un normale "Elenco". Per tutti gli scopi pratici, la 'BackedList' può essere vista come un altro 'Elenco' dal codice client.

Ciò ha senso dal punto di vista di una grande classe principale che potrebbe fornire accesso a uno o più componenti di elenco che sono essi stessi supportati da uno o più schemi in un database. Dato che i metodi C # non sono virtuali per impostazione predefinita, l'elenco fornito dalla classe principale non può essere un semplice IEnumerable o ICollection o persino un'istanza List ma deve invece essere pubblicizzato sul client come 'BackedList' per garantire che la nuova versione dell'operazione "Aggiungi" viene chiamato per aggiornare lo schema corretto.

Non è certamente un problema di prestazioni. L'interprete Java di Sun utilizza lo stesso codice per inviare ( codice invokevirtual ) e HotSpot genera esattamente lo stesso codice sia final o meno. Credo che tutti gli oggetti C # (ma non le strutture) abbiano metodi virtuali, quindi avrai sempre bisogno dell'identificazione della classe vtbl / runtime. C # è un dialetto di "linguaggi simili a Java". Suggerire che proviene dal C ++ non è del tutto onesto.

C'è l'idea che dovresti " progettare per ereditarietà o altrimenti vietarlo " ;. Il che suona come una grande idea fino al momento in cui hai un caso aziendale grave da mettere in una soluzione rapida. Forse ereditando dal codice che non controlli.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top