Perché i metodi dell'interfaccia C # non sono dichiarati astratti o virtuali?


108

I metodi C # nelle interfacce vengono dichiarati senza utilizzare la virtualparola chiave e sottoposti a override nella classe derivata senza utilizzare la overrideparola chiave.

C'è una ragione per questo? Presumo che sia solo una comodità di linguaggio, e ovviamente il CLR sa come gestirlo sotto le coperte (i metodi non sono virtuali di default), ma ci sono altri motivi tecnici?

Ecco l'IL generato da una classe derivata:

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

Si noti che il metodo è dichiarato virtual finalin IL.

Risposte:


145

Per l'interfaccia, l'aggiunta delle abstract, o anche delle publicparole chiave sarebbe ridondante, quindi le ometti:

interface MyInterface {
  void Method();
}

Nel CIL, il metodo è contrassegnato virtuale abstract.

(Notare che Java consente di dichiarare i membri dell'interfaccia public abstract).

Per la classe di implementazione, ci sono alcune opzioni:

Non sovrascrivibile : in C # la classe non dichiara il metodo come virtual. Ciò significa che non può essere sovrascritto in una classe derivata (solo nascosta). Nel CIL il metodo è ancora virtuale (ma sigillato) perché deve supportare il polimorfismo rispetto al tipo di interfaccia.

class MyClass : MyInterface {
  public void Method() {}
}

Sostituibile : sia in C # che in CIL il metodo è virtual. Partecipa all'invio polimorfico e può essere sovrascritto.

class MyClass : MyInterface {
  public virtual void Method() {}
}

Esplicito : questo è un modo per una classe di implementare un'interfaccia ma non di fornire i metodi di interfaccia nell'interfaccia pubblica della classe stessa. Nel CIL il metodo sarà private(!) Ma sarà comunque richiamabile dall'esterno della classe da un riferimento al tipo di interfaccia corrispondente. Anche le implementazioni esplicite non possono essere sovrascritte. Ciò è possibile perché esiste una direttiva CIL ( .override) che collegherà il metodo privato al metodo di interfaccia corrispondente che sta implementando.

[C #]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

In VB.NET, puoi anche creare un alias del nome del metodo di interfaccia nella classe di implementazione.

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

Ora, considera questo strano caso:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

Se Basee Derivedsono dichiarati nello stesso assembly, il compilatore renderà Base::Methodvirtuale e sigillato (nel CIL), anche se Basenon implementa l'interfaccia.

Se Basee si Derivedtrovano in assembly diversi, durante la compilazione Deriveddell'assembly, il compilatore non cambierà l'altro assembly, quindi introdurrà un membro in Derivedche sarà un'implementazione esplicita perché MyInterface::Methoddelegherà semplicemente la chiamata a Base::Method.

Quindi, vedete, ogni implementazione del metodo di interfaccia deve supportare un comportamento polimorfico, e quindi deve essere contrassegnata come virtuale nel CIL, anche se il compilatore deve fare i conti per farlo.


73

Citando Jeffrey Ritcher di CLR tramite CSharp 3rd Edition qui

Il CLR richiede che i metodi di interfaccia siano contrassegnati come virtuali. Se non si contrassegna esplicitamente il metodo come virtuale nel codice sorgente, il compilatore contrassegna il metodo come virtuale e sigillato; ciò impedisce a una classe derivata di sovrascrivere il metodo dell'interfaccia. Se si contrassegna esplicitamente il metodo come virtuale, il compilatore contrassegna il metodo come virtuale (e lo lascia non sigillato); ciò consente a una classe derivata di sovrascrivere il metodo dell'interfaccia. Se un metodo di interfaccia è bloccato, una classe derivata non può sovrascrivere il metodo. Tuttavia, una classe derivata può ereditare nuovamente la stessa interfaccia e può fornire la propria implementazione per i metodi dell'interfaccia.


25
La citazione non dice perché l'implementazione di un metodo di interfaccia deve essere contrassegnata come virtuale. È perché è polimorfico per quanto riguarda il tipo di interfaccia, quindi ha bisogno di uno slot sulla tabella virtuale per consentire l'invio del metodo virtuale.
Jordão

1
Non sono autorizzato a contrassegnare esplicitamente il metodo dell'interfaccia come virtuale e viene visualizzato l'errore "errore CS0106: il modificatore" virtuale "non è valido per questo elemento". Testato utilizzando v2.0.50727 (versione più vecchia sul mio PC).
ccppjava

3
@ccppjava Dal commento di Jorado di seguito, contrassegni il membro della classe che sta implementando l'interfaccia virtuale per consentire alle sottoclassi di sovrascrivere la classe.
Christopher Stevenson

13

Sì, i metodi di implementazione dell'interfaccia sono virtuali per quanto riguarda il runtime. È un dettaglio di implementazione, fa funzionare le interfacce. I metodi virtuali ottengono gli slot nella v-table della classe, ogni slot ha un puntatore a uno dei metodi virtuali. Il cast di un oggetto in un tipo di interfaccia genera un puntatore alla sezione della tabella che implementa i metodi di interfaccia. Il codice client che utilizza il riferimento all'interfaccia ora vede il primo puntatore al metodo dell'interfaccia all'offset 0 dal puntatore all'interfaccia, eccetera.

Quello che ho sottovalutato nella mia risposta originale è il significato dell'attributo finale . Impedisce a una classe derivata di sovrascrivere il metodo virtuale. Una classe derivata deve reimplementare l'interfaccia, i metodi di implementazione shadow i della classe base. Il che è sufficiente per implementare il contratto del linguaggio C # che dice che il metodo di implementazione non è virtuale.

Se dichiari il metodo Dispose () nella classe Example come virtuale, vedrai l' attributo finale essere rimosso. Ora consentendo a una classe derivata di sovrascriverlo.


4

Nella maggior parte degli altri ambienti di codice compilato, le interfacce sono implementate come vtables - un elenco di puntatori ai corpi del metodo. Tipicamente una classe che implementa più interfacce avrà da qualche parte nei metadati generati dal compilatore interno un elenco di vtable di interfaccia, una vtable per interfaccia (in modo da preservare l'ordine del metodo). In questo modo vengono implementate anche le interfacce COM.

In .NET, tuttavia, le interfacce non sono implementate come vtable distinte per ogni classe. I metodi di interfaccia sono indicizzati tramite una tabella di metodi di interfaccia globale di cui fanno parte tutte le interfacce. Pertanto, non è necessario dichiarare un metodo virtuale affinché quel metodo possa implementare un metodo di interfaccia: la tabella del metodo di interfaccia globale può puntare direttamente all'indirizzo del codice del metodo della classe.

La dichiarazione di un metodo virtuale per implementare un'interfaccia non è richiesta neanche in altri linguaggi, anche in piattaforme non CLR. La lingua Delphi su Win32 è un esempio.


0

Non sono virtuali (in termini di come le pensiamo, se non in termini di implementazione sottostante come (sigillato virtuale) - è bene leggere le altre risposte qui e imparare qualcosa da solo :-)

Non sovrascrivono nulla: non c'è implementazione nell'interfaccia.

Tutto ciò che l'interfaccia fa è fornire un "contratto" a cui la classe deve aderire - uno schema, se vuoi, in modo che i chiamanti sappiano come chiamare l'oggetto anche se non hanno mai visto quella particolare classe prima.

Spetta alla classe quindi implementare il metodo di interfaccia come vuole, entro i confini del contratto - virtuale o "non virtuale" (sigillato virtuale a quanto pare).


tutti in questo thread sanno a cosa servono le interfacce. La domanda è estremamente specifica: l'IL generato è virtuale per il metodo di interfaccia e non virtuale per un metodo non di interfaccia.
Rex M

5
Sì, è davvero facile criticare una risposta dopo che la domanda è stata modificata, non è vero?
Jason Williams

0

Le interfacce sono un concetto più astratto delle classi, quando dichiari una classe che implementa un'interfaccia, dici semplicemente "la classe deve avere questi metodi particolari dall'interfaccia, e non importa se statica , virtuale , non virtuale , sovrascritta , come purché abbia lo stesso ID e gli stessi parametri di tipo ".

Altri linguaggi che supportano interfacce come Object Pascal ("Delphi") e Objective-C (Mac) non richiedono che i metodi di interfaccia siano contrassegnati come virtuali e non virtuali.

Ma potresti avere ragione, penso che potrebbe essere una buona idea avere uno specifico attributo "virtuale" / "override" nelle interfacce, nel caso in cui desideri limitare i metodi delle classi che implementano una particolare interfaccia. Ma questo significa anche avere una parola chiave "non virtuale", "dontcareifvirtualornot", per entrambe le interfacce.

Capisco la tua domanda, perché vedo qualcosa di simile in Java, quando un metodo di classe deve utilizzare "@virtual" o "@override" per essere sicuro che un metodo sia inteso come virtuale.


@override non modifica effettivamente il comportamento del codice né altera il codice byte risultante. Quello che fa è segnalare al compilatore che il metodo così decorato è inteso come un override, che consente al compilatore di fare alcuni controlli di integrità. C # funziona in modo diverso; overrideè una parola chiave di prima classe nella lingua stessa.
Robert Harvey,
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.