Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
19.1 Generale
Un'interfaccia definisce un contratto. Una classe o uno struct che implementa un'interfaccia deve rispettare il contratto. Un'interfaccia può ereditare da più interfacce di base e una classe o uno struct può implementare più interfacce.
Le interfacce possono contenere vari tipi di membri, come descritto in §19.4. L'interfaccia stessa può fornire un'implementazione per alcuni o tutti i membri della funzione dichiarati. I membri per i quali l'interfaccia non fornisce un'implementazione sono astratti. Le implementazioni devono essere fornite da classi o struct che implementano l'interfaccia o l'interfaccia derivata che forniscono una definizione di override.
Nota: storicamente, l'aggiunta di un nuovo membro di funzione a un'interfaccia ha interessato tutti i consumer esistenti di tale tipo di interfaccia; era un cambiamento che causava un'interruzione. L'aggiunta di implementazioni membro della funzione di interfaccia consente agli sviluppatori di aggiornare un'interfaccia, consentendo comunque agli implementatori di eseguire l'override di tale implementazione. Gli utenti dell'interfaccia possono accettare l'implementazione come modifica non di rilievo; Tuttavia, se i requisiti sono diversi, possono eseguire l'override delle implementazioni fornite. nota finale
19.2 Dichiarazioni di interfaccia
19.2.1 Generale
Un interface_declaration è un type_declaration (§14.7) che dichiara un nuovo tipo di interfaccia.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
Un interface_declaration è costituito da un set facoltativo di attributi (§23), seguito da un set facoltativo di interface_modifiers (§19.2.2), seguito da un modificatore parziale facoltativo (§15.2.7), seguito dalla parola chiave interface e da un identificatore che denomina l'interfaccia, seguito da una specifica variant_type_parameter_listfacoltativa (§19.2.3), seguita da una specifica interface_base facoltativa (§19.2.4)), seguito da una specifica facoltativa type_parameter_constraints_clause(§15.2.5), seguita da un interface_body (§19.3), seguita facoltativamente da un punto e virgola.
Una dichiarazione di interfaccia non deve fornire type_parameter_constraints_clausea meno che non fornisca anche un variant_type_parameter_list.
Una dichiarazione di interfaccia che fornisce un variant_type_parameter_list è una dichiarazione di interfaccia generica. Inoltre, qualsiasi interfaccia annidata all'interno di una dichiarazione di classe generica o una dichiarazione di struct generica è una dichiarazione di interfaccia generica, poiché gli argomenti di tipo per il tipo contenitore devono essere forniti per creare un tipo costruito (§8.4).
19.2.2 Modificatori di interfaccia
Un interface_declaration può includere facoltativamente una sequenza di modificatori di interfaccia:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§24.2) è disponibile solo nel codice unsafe (§24).
Si tratta di un errore in fase di compilazione per visualizzare più volte lo stesso modificatore in una dichiarazione di interfaccia.
Il new modificatore è consentito solo nelle interfacce definite all'interno di una classe. Specifica che l'interfaccia nasconde un membro ereditato con lo stesso nome, come descritto in §15.3.5.
I publicmodificatori , protectedinternal, e private controllano l'accessibilità dell'interfaccia. A seconda del contesto in cui si verifica la dichiarazione di interfaccia, solo alcuni di questi modificatori possono essere consentiti (§7.5.2). Quando una dichiarazione di tipo parziale (§15.2.7) include una specifica di accessibilità (tramite i public, protected, internal e private modificatori), si applicano le regole in §15.2.2.
19.2.3 Elenchi di parametri di tipo varianti
19.2.3.1 Generale
Gli elenchi di parametri di tipo varianti sono consentiti solo nei tipi di interfaccia e delegato. La differenza rispetto ai type_parameter_list ordinari è l'variance_annotation facoltativa per ogni parametro di tipo.
variant_type_parameter_list
: '<' variant_type_parameter (',' variant_type_parameter)* '>'
;
variant_type_parameter
: attributes? variance_annotation? type_parameter
;
variance_annotation
: 'in'
| 'out'
;
Se l'annotazione della varianza è out, il parametro di tipo viene detto covariante. Se l'annotazione della varianza è in, si dice che il parametro di tipo sia controvariante. Se non è presente alcuna annotazione di varianza, si dice che il parametro di tipo sia invariante.
Esempio: nell'esempio seguente:
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
Xè covariante,Yè controvariante edZè invariante.esempio finale
Se un'interfaccia generica viene dichiarata in più parti (§15.2.3), ogni dichiarazione parziale specifica la stessa varianza per ogni parametro di tipo.
19.2.3.2 Sicurezza della varianza
L'occorrenza di annotazioni di varianza nell'elenco dei parametri di tipo di un tipo limita le posizioni in cui i tipi possono verificarsi all'interno della dichiarazione di tipo.
Un tipo T è non sicuro per l'output se si verifica una delle seguenti condizioni:
-
Tè un parametro di tipo controvariante -
Tè un tipo di array con un tipo di elemento non sicuro per l'output -
Tè un tipoSᵢ,... Aₑdi interfaccia o delegato costruito da un tipoS<Xᵢ, ... Xₑ>generico in cui per almeno uno dei seguenti casiAᵢè vero:-
Xᵢè covariante o invariante edAᵢè output-unsafe. -
Xᵢè controvariante o invariante edAᵢè input-unsafe.
-
Un tipo T è input insicuro se valgono una delle seguenti condizioni:
-
Tè un parametro di tipo covariante -
Tè un tipo di matrice con un tipo di elemento non sicuro per l'input -
Tè un tipoS<Aᵢ,... Aₑ>di interfaccia o delegato costruito da un tipoS<Xᵢ, ... Xₑ>generico in cui per almeno uno dei seguenti casiAᵢè vero:-
Xᵢè covariante o invariante eAᵢnon è sicuro per l'input. -
Xᵢè controvariante o invariante edAᵢè output-unsafe.
-
In modo intuitivo, un tipo output-unsafe non è consentito in una posizione di output e un tipo input-unsafe non è consentito in una posizione di input.
Un tipo è sicuro per l'output se non è non sicuro per l'output, e sicuro per l'input se non è non sicuro per l'input.
19.2.3.3 Conversione varianza
Lo scopo delle annotazioni di varianza è consentire conversioni più morbide (ma ancora sicure dal punto di vista del tipo) in tipi di interfaccia e delegati. A tale scopo, le definizioni di conversioni implicite (§10.2) e esplicite (§10.3) usano la nozione di variabilità convertibilità, definita come segue:
Un tipo T<Aᵢ, ..., Aᵥ> è convertibile per varianza in un tipo T<Bᵢ, ..., Bᵥ> se T è un'interfaccia o un tipo delegato dichiarato con i T<Xᵢ, ..., Xᵥ> parametri di tipo variante, e per ogni parametro di tipo variante Xᵢ vale una delle seguenti condizioni:
-
Xᵢè covariante e esiste una conversione implicita di riferimento o identità daAᵢaBᵢ -
Xᵢè controvariante e esiste una conversione implicita di riferimento o identità daBᵢaAᵢ -
Xᵢè invariante e esiste una conversione di identità daAᵢaBᵢ
19.2.4 Interfacce di base
Un'interfaccia può ereditare da zero o più tipi di interfaccia, denominati interfacce di base esplicitedell'interfaccia. Quando un'interfaccia dispone di una o più interfacce di base esplicite, nella dichiarazione di tale interfaccia l'identificatore di interfaccia è seguito da due punti e da un elenco delimitato da virgole di tipi di interfaccia di base.
Un'interfaccia derivata può dichiarare nuovi membri che nascondono i membri ereditati (§7.7.2.3) dichiarati nelle interfacce di base o implementano in modo esplicito i membri ereditati (§19.6.2) dichiarati nelle interfacce di base.
interface_base
: ':' interface_type_list
;
Le interfacce di base esplicite possono essere costruite tipi di interfaccia (§8.4, §19.2). Un'interfaccia di base non può essere un parametro di tipo autonomamente, anche se può coinvolgere i parametri di tipo inclusi nell'ambito.
Per un tipo di interfaccia costruito, le interfacce di base esplicite vengono formate prendendo le dichiarazioni di interfaccia di base esplicite sulla dichiarazione di tipo generico e sostituendo, per ogni type_parameter nella dichiarazione dell'interfaccia di base, il type_argument corrispondente del tipo costruito.
Le interfacce di base esplicite di un'interfaccia devono essere accessibili almeno quanto l'interfaccia stessa (§7.5.5).
Nota: ad esempio, si tratta di un errore in fase di compilazione per specificare un'interfaccia
privateointernalnella interface_base di un'interfacciapublic. nota finale
Si tratta di un errore in fase di compilazione per un'interfaccia che eredita direttamente o indirettamente da sé stessa.
L'interfaccia di base di un'interfacciasono le interfacce di base esplicite e le relative interfacce di base. In altre parole, il set di interfacce di base è la chiusura transitiva completa delle interfacce di base esplicite, le relative interfacce di base esplicite e così via. Un'interfaccia eredita tutti i membri delle relative interfacce di base.
Esempio: nel codice seguente
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}le interfacce di base di
IComboBoxsonoIControl,ITextBoxeIListBox. In altre parole, l'interfacciaIComboBoxprecedente eredita i membriSetTexteSetItemsPaint.esempio finale
I membri ereditati da un tipo generico costruito vengono ereditati dopo la sostituzione del tipo. Ovvero, tutti i tipi costitutivi nel membro hanno i parametri di tipo della dichiarazione di classe base sostituiti con gli argomenti di tipo corrispondenti usati nella specifica class_base .
Esempio: nel codice seguente
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }l'interfaccia
IDerivederedita ilCombinemetodo dopo che il parametroTdi tipo viene sostituito constring[,].esempio finale
Una classe o uno struct che implementa un'interfaccia implementa in modo implicito anche tutte le interfacce di base dell'interfaccia.
La gestione delle interfacce su più parti di una dichiarazione di interfaccia parziale (§15.2.7) è illustrata più avanti in §15.2.4.3.
Ogni interfaccia di base di un'interfaccia deve essere protetta dall'output (§19.2.3.2).
19.3 Corpo dell'interfaccia
Il interface_body di un'interfaccia definisce i membri dell'interfaccia.
interface_body
: '{' interface_member_declaration* '}'
;
19.4 Membri dell'interfaccia
19.4.1 Generale
I membri di un'interfaccia sono i membri ereditati dalle interfacce di base e i membri dichiarati dall'interfaccia stessa.
interface_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| static_constructor_declaration
| operator_declaration
| type_declaration
;
Questa clausola aumenta la descrizione dei membri nelle classi (§15.3) con restrizioni per le interfacce. I membri interface vengono dichiarati usando member_declarations con le regole aggiuntive seguenti:
- Non è consentito un finalizer_declaration .
- I costruttori di istanza, constructor_declarations, non sono consentiti.
- Tutti i membri dell'interfaccia dispongono implicitamente dell'accesso pubblico; Tuttavia, è consentito un modificatore di accesso esplicito (§7.5.2), ad eccezione dei costruttori statici (§15.12).
- Il
abstractmodificatore è implicito per i membri della funzione di interfaccia senza corpi; tale modificatore può essere fornito in modo esplicito. - Membro della funzione dell'istanza dell'interfaccia la cui dichiarazione include un corpo è un membro in modo
virtualimplicito, a meno che non venga utilizzato ilsealedmodificatore oprivate. Ilvirtualmodificatore può essere fornito in modo esplicito. - Un
privatemembro osealeddi una funzione di un'interfaccia deve avere un corpo. - Un
privatemembro della funzione non deve avere il modificatoresealed. - Un'interfaccia derivata può eseguire l'override di un membro astratto o virtuale dichiarato in un'interfaccia di base.
- Un membro della funzione implementato in modo esplicito non deve avere il modificatore
sealed.
Alcune dichiarazioni, ad esempio constant_declaration (§15.4) non hanno restrizioni nelle interfacce.
I membri ereditati di un'interfaccia non fanno parte specificamente dello spazio di dichiarazione dell'interfaccia. Pertanto, un'interfaccia può dichiarare un membro con lo stesso nome o firma di un membro ereditato. In questo caso, si dice che il membro dell'interfaccia derivata nasconda il membro dell'interfaccia di base. Nascondere un membro ereditato non è considerato un errore, ma genera un avviso (§7.7.2.3).
Se un new modificatore è incluso in una dichiarazione che non nasconde un membro ereditato, viene emesso un avviso di conseguenza.
Nota: i membri della classe
objectnon sono, in senso stretto, membri di qualsiasi interfaccia (§19.4). Tuttavia, i membri della classeobjectsono disponibili tramite la ricerca dei membri in qualsiasi tipo di interfaccia (§12.5). nota finale
Il set di membri di un'interfaccia dichiarata in più parti (§15.2.7) è l'unione dei membri dichiarati in ogni parte. I corpi di tutte le parti della dichiarazione di interfaccia condividono lo stesso spazio di dichiarazione (§7.3) e l'ambito di ogni membro (§7.7) si estende ai corpi di tutte le parti.
Esempio: si consideri un'interfaccia
IAcon un'implementazione per un membroMe una proprietàP. Un tipoCdi implementazione non fornisce un'implementazione perMoP. È necessario accedervi tramite un riferimento il cui tipo in fase di compilazione è un'interfaccia convertibile in modo implicito inIAoIB. Questi membri non vengono trovati tramite la ricerca dei membri in una variabile di tipoC.interface IA { public int P { get { return 10; } } public void M() { Console.WriteLine("IA.M"); } } interface IB : IA { public new int P { get { return 20; } } void IA.M() { Console.WriteLine("IB.M"); } } class C : IB { } class Test { public static void Main() { C c = new C(); ((IA)c).M(); // cast needed Console.WriteLine($"IA.P = {((IA)c).P}"); // cast needed Console.WriteLine($"IB.P = {((IB)c).P}"); // cast needed } }All'interno delle interfacce
IAeIB, il membroMè accessibile direttamente in base al nome. Tuttavia, all'interno del metodoMain, non è possibile scriverec.M()oc.P, perché tali nomi non sono visibili. Per trovarli, è necessario eseguire il cast al tipo di interfaccia appropriato. La dichiarazione diMinIBusa la sintassi esplicita di implementazione dell'interfaccia. Ciò è necessario per eseguire l'override di tale metodo inIA. Il modificatoreoverridepotrebbe non essere applicato a un membro della funzione. esempio finale
19.4.2 Campi interfaccia
Questa clausola aumenta la descrizione dei campi nelle classi §15.5 per i campi dichiarati nelle interfacce.
I campi di interfaccia vengono dichiarati utilizzando field_declarations (§15.5.1) con le regole aggiuntive seguenti:
- Si tratta di un errore in fase di compilazione per field_declaration dichiarare un campo di istanza.
Esempio: il programma seguente contiene membri statici di vari tipi:
public interface IX { public const int Constant = 100; protected static int field; static IX() { Console.WriteLine("static members initialized"); Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}"); field = 50; Console.WriteLine("static constructor has run"); } } public class Test: IX { public static void Main() { Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}"); } }L'output prodotto è
static members initialized constant = 100, field = 0 static constructor has run constant = 100, field = 50esempio finale
Per informazioni sull'allocazione e l'inizializzazione dei campi statici, vedere §19.4.8 .
19.4.3 Metodi di interfaccia
Questa clausola aumenta la descrizione dei metodi nelle classi §15.6 per i metodi dichiarati nelle interfacce.
I metodi di interfaccia vengono dichiarati usando method_declaration(§15.6)). Gli attributi, return_type, ref_return_type, identificatore e parameter_list di una dichiarazione di metodo di interfaccia hanno lo stesso significato di quelli di una dichiarazione di metodo in una classe . I metodi di interfaccia hanno le regole aggiuntive seguenti:
method_modifier non includerà
override.Un metodo il cui corpo è un punto e virgola (
;) èabstract; ilabstractmodificatore non è obbligatorio, ma è consentito.Una dichiarazione del metodo di interfaccia con corpo di blocco o corpo dell'espressione come method_body è
virtual; ilvirtualmodificatore non è obbligatorio, ma è consentito.Un method_declaration non avrà type_parameter_constraints_clausea meno che non abbia anche un type_parameter_list.
L'elenco dei requisiti per combinazioni valide di modificatori indicati per un metodo di classe viene esteso, come indicato di seguito:
- Una dichiarazione statica che non è extern deve avere un corpo del blocco o un corpo dell'espressione come method_body.
- Una dichiarazione virtuale che non è extern deve avere un corpo del blocco o un corpo dell'espressione come method_body.
- Una dichiarazione privata che non è extern deve avere un corpo del blocco o un corpo dell'espressione come method_body.
- Una dichiarazione sealed che non è extern deve avere un corpo del blocco o un corpo dell'espressione come method_body.
- Una dichiarazione asincrona deve avere un corpo del blocco o un corpo dell'espressione come method_body.
Tutti i tipi di parametro di un metodo di interfaccia devono essere indipendenti dall'input (§19.2.3.2) e il tipo restituito deve essere
voido indipendente dall'output.Anche qualsiasi tipo di parametro di output o riferimento deve essere indipendente dall'output.
Nota: i parametri di output devono essere sicuri per l'input a causa di restrizioni di implementazione comuni. nota finale
Ogni vincolo di tipo di classe, vincolo del tipo di interfaccia e vincolo di parametro di tipo per qualsiasi parametro di tipo del metodo deve essere indipendente dall'input.
Queste regole assicurano che qualsiasi utilizzo covariante o controvariante dell'interfaccia rimanga typesafe.
Esempio:
interface I<out T> { void M<U>() where U : T; // Error }non è valido perché l'utilizzo di
Tcome vincolo del parametro di tipo suUnon è sicuro per l'uso come input.Se questa restrizione non fosse in vigore, sarebbe possibile violare la sicurezza dei tipi nel modo seguente:
interface I<out T> { void M<U>() where U : T; } class B {} class D : B {} class E : B {} class C : I<D> { public void M<D>() {...} } ... I<B> b = new C(); b.M<E>();Si tratta in realtà di una chiamata a
C.M<E>. Tuttavia, questa chiamata richiede cheEderivi daD, quindi la sicurezza dei tipi verrebbe violata qui.esempio finale
Nota: vedere §19.4.2 per un esempio che non solo mostra un metodo statico con un'implementazione, ma poiché tale metodo viene chiamato
Maine ha il tipo restituito e la firma corretti, è anche un punto di ingresso. nota finale
Un metodo virtuale con implementazione dichiarata in un'interfaccia può essere sottoposto a override per essere astratto in un'interfaccia derivata. Questa operazione è nota come riabstrazione.
Esempio:
interface IA { void M() { Console.WriteLine("IA.M"); } } interface IB: IA { abstract void IA.M(); // reabstraction of M }Ciò è utile nelle interfacce derivate in cui l'implementazione di un metodo non è appropriata e deve essere fornita un'implementazione più appropriata implementando le classi. esempio finale
19.4.4 Proprietà dell'interfaccia
Questa clausola aumenta la descrizione delle proprietà nelle classi §15.7 per le proprietà dichiarate nelle interfacce.
Le proprietà dell'interfaccia vengono dichiarate utilizzando property_declarations (§15.7.1) con le regole aggiuntive seguenti:
property_modifier non include
override.Un'implementazione esplicita dei membri dell'interfaccia non deve contenere un accessor_modifier (§15.7.3).
Un'interfaccia derivata può implementare in modo esplicito una proprietà di interfaccia astratta dichiarata in un'interfaccia di base.
Nota: poiché un'interfaccia non può contenere campi di istanza, una proprietà di interfaccia non può essere una proprietà automatica dell'istanza, perché ciò richiederebbe la dichiarazione di campi di istanza nascosti impliciti. nota finale
Il tipo di una proprietà di interfaccia deve essere sicuro per l'output se è presente un metodo di accesso get e deve essere sicuro per l'input se è presente un metodo di accesso set.
Una dichiarazione del metodo di interfaccia con corpo di blocco o corpo dell'espressione come method_body è
virtual; ilvirtualmodificatore non è obbligatorio, ma è consentito.Un'istanza property_declaration senza implementazione è
abstract; ilabstractmodificatore non è obbligatorio, ma è consentito. Non viene mai considerata una proprietà implementata automaticamente (§15.7.4).
19.4.5 Eventi dell'interfaccia
Questa clausola aumenta la descrizione degli eventi nelle classi §15.8 per gli eventi dichiarati nelle interfacce.
Gli eventi di interfaccia vengono dichiarati utilizzando event_declarations (§15.8.1), con le regole aggiuntive seguenti:
-
event_modifier non include
override. - Un'interfaccia derivata può implementare un evento di interfaccia astratta dichiarato in un'interfaccia di base (§15.8.5).
- Si tratta di un errore in fase di compilazione per variable_declarators in un'istanza di event_declaration contenere qualsiasi variable_initializers.
- Un evento di istanza con i
virtualmodificatori osealeddeve dichiarare le funzioni di accesso. Non è mai considerato un evento di tipo campo implementato automaticamente (§15.8.2). - Un evento di istanza con il
abstractmodificatore non deve dichiarare le funzioni di accesso. - Il tipo di un evento di interfaccia deve essere sicuro per l'input.
19.4.6 Indicizzatori di interfaccia
Questa clausola aumenta la descrizione degli indicizzatori nelle classi §15.9 per gli indicizzatori dichiarati nelle interfacce.
Gli indicizzatori di interfaccia vengono dichiarati utilizzando indexer_declarations (§15.9), con le regole aggiuntive seguenti:
indexer_modifier non includerà
override.Un indexer_declaration che ha un corpo dell'espressione o contiene una funzione di accesso con un corpo del blocco o di un corpo dell'espressione è
virtual; ilvirtualmodificatore non è obbligatorio, ma è consentito.Un indexer_declaration i cui corpi delle funzioni di accesso sono punti e virgola (
;) èabstract; ilabstractmodificatore non è obbligatorio, ma è consentito.Tutti i tipi di parametro di un indicizzatore di interfaccia devono essere indipendenti dall'input (§19.2.3.2).
Anche qualsiasi tipo di parametro di output o riferimento deve essere indipendente dall'output.
Nota: i parametri di output devono essere sicuri per l'input a causa di restrizioni di implementazione comuni. nota finale
Il tipo di indicizzatore di interfaccia deve essere sicuro per l'output se è presente un metodo di accesso get e deve essere sicuro per l'input se è presente un metodo di accesso set.
19.4.7 Operatori di interfaccia
Questa clausola aumenta la descrizione dei membri operator_declaration nelle classi §15.10 per gli operatori dichiarati nelle interfacce.
Un operator_declaration in un'interfaccia è l'implementazione (§19.1).
Si tratta di un errore in fase di compilazione per un'interfaccia per dichiarare un operatore di conversione, uguaglianza o disuguaglianza.
19.4.8 Costruttori statici dell'interfaccia
Questa clausola aumenta la descrizione dei costruttori statici nelle classi §15.12 per i costruttori statici dichiarati nelle interfacce.
Il costruttore statico per un'interfaccia chiusa (§8.4.3) viene eseguito al massimo una volta in un determinato dominio applicazione. L'esecuzione di un costruttore statico viene attivata dalla prima delle azioni seguenti da eseguire all'interno di un dominio applicazione:
- Viene fatto riferimento a uno dei membri statici dell'interfaccia.
- Prima che il
Mainmetodo venga chiamato per un'interfaccia contenente ilMainmetodo (§7.1) in cui inizia l'esecuzione. - Tale interfaccia fornisce un'implementazione per un membro e tale implementazione è accessibile come implementazione più specifica (§19.4.10) per tale membro.
Nota: nel caso in cui non venga eseguita nessuna delle azioni precedenti, il costruttore statico per un'interfaccia potrebbe non essere eseguito per un programma in cui vengono create e usate istanze di tipi che implementano l'interfaccia. nota finale
Per inizializzare un nuovo tipo di interfaccia chiusa, viene creato un nuovo set di campi statici per quel particolare tipo chiuso. Ogni campo statico viene inizializzato sul valore predefinito. Successivamente, gli inizializzatori di campo statici vengono eseguiti per tali campi statici. Infine, viene eseguito il costruttore statico.
Nota: vedere §19.4.2 per un esempio di utilizzo di vari tipi di membri statici (incluso un metodo Main) dichiarato all'interno di un'interfaccia. nota finale
19.4.9 Tipi annidati di interfaccia
Questa clausola aumenta la descrizione dei tipi annidati nelle classi §15.3.9 per i tipi annidati dichiarati nelle interfacce.
Si tratta di un errore per dichiarare un tipo di classe, un tipo struct o un tipo enumerazione nell'ambito di un parametro di tipo dichiarato con un variance_annotation (§19.2.3.1).
Esempio: la dichiarazione di
Cseguito è un errore.interface IOuter<out T> { class C { } // error: class declaration within scope of variant type parameter 'T' }esempio finale
19.4.10 implementazione più specifica
Ogni classe e struct deve avere un'implementazione più specifica per ogni membro virtuale dichiarato in tutte le interfacce implementate da tale tipo tra le implementazioni visualizzate nel tipo o nelle relative interfacce dirette e indirette. L'implementazione più specifica è un'implementazione univoca più specifica di ogni altra implementazione.
Nota: la regola di implementazione più specifica garantisce che un'ambiguità derivante dall'ereditarietà dell'interfaccia a rombo venga risolta in modo esplicito dal programmatore nel punto in cui si verifica il conflitto. nota finale
Per un tipo T che è uno struct o una classe che implementa le interfacce I2 e I3, dove I2 e derivano I3 direttamente o indirettamente dall'interfaccia I che dichiara un membro M, l'implementazione più specifica di M è:
- Se
Tdichiara un'implementazione diI.M, tale implementazione è l'implementazione più specifica. - In caso contrario, se
Tè una classe e una classe base diretta o indiretta dichiara un'implementazione diI.M, la classe base più derivata diTè l'implementazione più specifica. - In caso contrario, se
I2eI3sono interfacce implementate daTeI3derivano direttamenteI2o indirettamente,I3.Mè un'implementazione più specifica diI2.M. - In caso contrario, né
I2.MI3.Msono più specifici e si verifica un errore.
Esempio:
interface IA { void M() { Console.WriteLine("IA.M"); } } interface IB : IA { void IA.M() { Console.WriteLine("IB.M"); } } interface IC: IA { void IA.M() { Console.WriteLine("IC.M"); } } abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M' abstract class D: IA, IB, IC // OK { public abstract void M(); }La regola di implementazione più specifica garantisce che un conflitto (ad esempio, un'ambiguità derivante dall'ereditarietà dei diamanti) venga risolta in modo esplicito dal programmatore nel punto in cui si verifica il conflitto. esempio finale
19.4.11 Accesso ai membri dell'interfaccia
I membri dell'interfaccia sono accessibili tramite l'accesso ai membri (§12.8.7) e l'accesso dell'indicizzatore (§12.8.12.4) del modulo I.M e I[A], dove I è un tipo di interfaccia, M è una costante, un campo, un metodo, una proprietà o un evento di tale tipo di interfaccia ed A è un elenco di argomenti dell'indicizzatore.
In una classe , con classe DBbase diretta o indiretta , in cui B implementa direttamente o indirettamente l'interfaccia I e I definisce un metodo M(), l'espressione base.M() è valida solo se base.M() staticamente (§12.3) si associa a un'implementazione di in un tipo di M() classe.
Per le interfacce rigorosamente a ereditarietà singola (ogni interfaccia della catena di ereditarietà ha esattamente zero o un'interfaccia di base diretta), gli effetti delle regole di ricerca membro (§12.5), chiamata al metodo (§12.8.10.2) e accesso dell'indicizzatore (§12.8.12.4) sono esattamente uguali a per le classi e gli struct: più membri derivati nascondono membri meno derivati con lo stesso nome o firma. Tuttavia, per le interfacce di ereditarietà multipla, possono verificarsi ambiguità quando due o più interfacce di base non correlate dichiarano membri con lo stesso nome o firma. Questa sottochiave mostra diversi esempi, alcuni dei quali portano ad ambiguità e altri che non lo fanno. In tutti i casi, è possibile usare conversioni esplicite per risolvere le ambiguità.
Esempio: nel codice seguente
interface IList { int Count { get; set; } } interface ICounter { int Count { get; set; } } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count } }La prima istruzione causa un errore in fase di compilazione perché la ricerca del membro (§12.5) di
CountinIListCounterè ambigua. Come illustrato nell'esempio, l'ambiguità viene risolta eseguendo il castxal tipo di interfaccia di base appropriato. Tali cast non hanno costi di runtime, ma sono semplicemente costituiti dalla visualizzazione dell'istanza come tipo meno derivato in fase di compilazione.esempio finale
Esempio: nel codice seguente
interface IInteger { void Add(int i); } interface IDouble { void Add(double d); } interface INumber : IInteger, IDouble {} class C { void Test(INumber n) { n.Add(1); // Invokes IInteger.Add n.Add(1.0); // Only IDouble.Add is applicable ((IInteger)n).Add(1); // Only IInteger.Add is a candidate ((IDouble)n).Add(1); // Only IDouble.Add is a candidate } }la chiamata
n.Add(1)selezionaIInteger.Addapplicando le regole di risoluzione dell'overload di §12.6.4. Analogamente, la chiamatan.Add(1.0)selezionaIDouble.Add. Quando vengono inseriti cast espliciti, esiste un solo metodo candidato e quindi nessuna ambiguità.esempio finale
Esempio: nel codice seguente
interface IBase { void F(int i); } interface ILeft : IBase { new void F(int i); } interface IRight : IBase { void G(); } interface IDerived : ILeft, IRight {} class A { void Test(IDerived d) { d.F(1); // Invokes ILeft.F ((IBase)d).F(1); // Invokes IBase.F ((ILeft)d).F(1); // Invokes ILeft.F ((IRight)d).F(1); // Invokes IBase.F } }il
IBase.Fmembro è nascosto dalILeft.Fmembro. La chiamatad.F(1)selezionaILeft.Fquindi , anche seIBase.Fsembra non essere nascosta nel percorso di accesso che conduce attraversoIRight.La regola intuitiva per nascondere le interfacce di ereditarietà multipla è semplicemente questa: se un membro è nascosto in qualsiasi percorso di accesso, è nascosto in tutti i percorsi di accesso. Poiché il percorso di accesso da
IDerivedaILeftaIBasenascondeIBase.F, il membro viene nascosto anche nel percorso di accesso daIDerivedaIRightaIBase.esempio finale
19.5 Nomi di membri di interfaccia qualificati
Un membro di interfaccia viene talvolta indicato dal nome completo del membro dell'interfaccia. Il nome completo di un membro di interfaccia è costituito dal nome dell'interfaccia in cui viene dichiarato il membro, seguito da un punto, seguito dal nome del membro. Il nome completo di un membro fa riferimento all'interfaccia in cui viene dichiarato il membro.
Esempio: Data la dichiarazione
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }il nome completo di
PaintèIControl.Painte il nome completo di SetText èITextBox.SetText. Nell'esempio precedente non è possibile fare riferimento aPaint.ITextBox.Paintesempio finale
Quando un'interfaccia fa parte di uno spazio dei nomi, un nome di membro di interfaccia qualificato può includere il nome dello spazio dei nomi.
Esempio:
namespace GraphicsLib { interface IPolygon { void CalculateArea(); } }All'interno dello spazio dei nomi di
GraphicsLib, siaIPolygon.CalculateAreacheGraphicsLib.IPolygon.CalculateAreasono nomi di membri di interfaccia qualificati per il metodoCalculateArea.esempio finale
19.6 Implementazioni dell'interfaccia
19.6.1 Generale
Le interfacce possono essere implementate da classi e struct. Per indicare che una classe o uno struct implementa direttamente un'interfaccia, l'interfaccia viene inclusa nell'elenco di classi base della classe o dello struct.
Una classe o uno struct C che implementa un'interfaccia I deve fornire o ereditare un'implementazione per ogni membro dichiarato in I che C può accedere. I membri pubblici di I possono essere definiti nei membri pubblici di C. I membri non pubblici dichiarati in che sono accessibili in IC possono essere definiti usando C l'implementazione esplicita dell'interfaccia (§19.6.2).
Un membro in un tipo derivato che soddisfa il mapping dell'interfaccia (§19.6.5) ma non implementa il membro dell'interfaccia di base corrispondente introduce un nuovo membro. Ciò si verifica quando è necessaria l'implementazione esplicita dell'interfaccia per definire il membro dell'interfaccia.
Esempio:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }esempio finale
Una classe o uno struct che implementa direttamente un'interfaccia implementa in modo implicito anche tutte le interfacce di base dell'interfaccia. Questo vale anche se la classe o lo struct non elenca in modo esplicito tutte le interfacce di base nell'elenco delle classi di base.
Esempio:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }In questo caso, la classe
TextBoximplementa siaIControlcheITextBox.esempio finale
Quando una classe C implementa direttamente un'interfaccia, tutte le classi derivate da C implementano anche l'interfaccia in modo implicito.
Le interfacce di base specificate in una dichiarazione di classe possono essere costruiti tipi di interfaccia (§8.4, §19.2).
Esempio: il codice seguente illustra come una classe può implementare tipi di interfaccia costruiti:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}esempio finale
Le interfacce di base di una dichiarazione di classe generica soddisfano la regola di univocità descritta in §19.6.3.
19.6.2 Implementazioni esplicite dei membri dell'interfaccia
Ai fini dell'implementazione di interfacce, una classe, uno struct o un'interfaccia possono dichiarare implementazioni esplicite dei membri dell'interfaccia. Un'implementazione esplicita del membro dell'interfaccia è una dichiarazione di metodo, proprietà, evento o indicizzatore che fa riferimento a un nome di membro di interfaccia qualificato. Una classe o uno struct che implementa un membro non pubblico in un'interfaccia di base deve dichiarare un'implementazione esplicita del membro dell'interfaccia. Un'interfaccia che implementa un membro in un'interfaccia di base deve dichiarare un'implementazione esplicita del membro dell'interfaccia.
Un membro di interfaccia derivato che soddisfa il mapping dell'interfaccia (§19.6.5) nasconde il membro dell'interfaccia di base (§7.7.2). Il compilatore genera un avviso a meno che il new modificatore non sia presente.
Esempio:
interface IList<T> { T[] GetElements(); } interface IDictionary<K, V> { V this[K key] { get; } void Add(K key, V value); } class List<T> : IList<T>, IDictionary<int, T> { public T[] GetElements() {...} T IDictionary<int, T>.this[int index] {...} void IDictionary<int, T>.Add(int index, T value) {...} }Qui
IDictionary<int,T>.thiseIDictionary<int,T>.Addsono implementazioni esplicite dei membri dell'interfaccia.esempio finale
Esempio: in alcuni casi, il nome di un membro di interfaccia potrebbe non essere appropriato per la classe di implementazione, nel qual caso il membro dell'interfaccia può essere implementato usando l'implementazione esplicita del membro dell'interfaccia. Una classe che implementa un'astrazione di file, ad esempio, implementerebbe probabilmente una
Closefunzione membro che ha l'effetto di rilasciare la risorsa file e implementare ilDisposemetodo dell'interfaccia usando l'implementazione esplicita del membro dell'interfacciaIDisposable:interface IDisposable { void Dispose(); } class MyFile : IDisposable { void IDisposable.Dispose() => Close(); public void Close() { // Do what's necessary to close the file System.GC.SuppressFinalize(this); } }esempio finale
Non è possibile accedere a un'implementazione esplicita del membro dell'interfaccia tramite il nome qualificato del membro dell'interfaccia in una chiamata di metodo, un accesso a proprietà, un accesso a eventi o un accesso a indicizzatori. È possibile accedere a un'implementazione esplicita del membro dell'istanza dell'interfaccia solo tramite un'istanza di interfaccia ed è in questo caso fatto riferimento semplicemente dal nome del membro. È possibile accedere a un'implementazione esplicita del membro statico dell'interfaccia solo tramite il nome dell'interfaccia.
Si tratta di un errore in fase di compilazione per un'implementazione esplicita del membro dell'interfaccia per includere eventuali modificatori (§15.6) diversi da extern o async.
Un'implementazione esplicita del metodo di interfaccia eredita tutti i vincoli di parametro di tipo dall'interfaccia .
Un type_parameter_constraints_clause su un'implementazione esplicita del metodo di interfaccia può essere costituito solo da class o structprimary_constraintapplicati a type_parameterche sono noti in base ai vincoli ereditati rispettivamente come tipo riferimento o valore. Qualsiasi tipo di modulo T? nella firma dell'implementazione esplicita del metodo di interfaccia, dove T è un parametro di tipo, viene interpretato come segue:
- Se si aggiunge un vincolo per il
classTparametro di tipo,T?è un tipo di riferimento annullabile; in caso contrario. - Se non è presente alcun vincolo aggiunto, o viene aggiunto un vincolo
struct, allora il parametro di tipoTè un tipo di valore nullableT?.
Esempio: di seguito viene illustrato il funzionamento delle regole quando sono coinvolti i parametri di tipo:
#nullable enable interface I { void Foo<T>(T? value) where T : class; void Foo<T>(T? value) where T : struct; } class C : I { void I.Foo<T>(T? value) where T : class { } void I.Foo<T>(T? value) where T : struct { } }Senza il vincolo
where T : classdel parametro di tipo , non è possibile eseguire l'override del metodo di base con il parametro di tipo di riferimento. esempio finale
Nota: le implementazioni esplicite dei membri dell'interfaccia hanno caratteristiche di accessibilità diverse rispetto ad altri membri. Poiché le implementazioni esplicite dei membri dell'interfaccia non sono mai accessibili tramite un nome di membro di interfaccia qualificato in una chiamata al metodo o un accesso alle proprietà, sono in un senso privato. Tuttavia, poiché è possibile accedervi tramite l'interfaccia, sono in un certo senso anche pubblico come l'interfaccia in cui sono dichiarati. Le implementazioni esplicite dei membri dell'interfaccia servono due scopi principali:
- Poiché le implementazioni esplicite dei membri dell'interfaccia non sono accessibili tramite istanze di classe o struct, consentono di escludere le implementazioni dell'interfaccia dall'interfaccia pubblica di una classe o di uno struct. Ciò è particolarmente utile quando una classe o uno struct implementa un'interfaccia interna che non è di interesse per un consumer di tale classe o struct.
- Le implementazioni esplicite dei membri dell'interfaccia consentono di disambiguare i membri dell'interfaccia con la stessa firma. Senza implementazioni esplicite dei membri dell'interfaccia, sarebbe impossibile per una classe, uno struct o un'interfaccia avere implementazioni diverse di membri di interfaccia con la stessa firma e tipo restituito, come sarebbe impossibile per una classe, uno struct o un'interfaccia avere un'implementazione in tutti i membri dell'interfaccia con la stessa firma ma con tipi restituiti diversi.
nota finale
Affinché un'implementazione esplicita del membro dell'interfaccia sia valida, la classe, lo struct o l'interfaccia denominano un'interfaccia nella relativa classe di base o nell'elenco di interfacce di base che contiene un membro il cui nome, tipo, numero di parametri di tipo e tipi di parametro corrispondono esattamente a quelli dell'implementazione esplicita del membro dell'interfaccia. Se un membro della funzione di interfaccia dispone di una matrice di parametri, il parametro corrispondente di un'implementazione esplicita del membro dell'interfaccia associata è consentito, ma non necessario, per avere il params modificatore. Se il membro della funzione di interfaccia non dispone di una matrice di parametri, un'implementazione esplicita del membro dell'interfaccia associata non avrà una matrice di parametri.
Esempio: Pertanto, nella classe seguente
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }la dichiarazione di
IComparable.CompareTorestituisce un errore di compilazione in fase di perchéIComparablenon è elencata nell'elenco di classi di base diShapee non è un'interfaccia di base diICloneable. Analogamente, nelle dichiarazioniclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }la dichiarazione di
ICloneable.CloneEllipsein genera un errore in fase di compilazione perchéICloneablenon è elencata in modo esplicito nell'elenco di classi di base diEllipse.esempio finale
Il nome qualificato del membro dell'interfaccia di un'implementazione esplicita del membro dell'interfaccia deve fare riferimento all'interfaccia in cui il membro è stato dichiarato.
Esempio: così, nelle dichiarazioni
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }L'implementazione esplicita del membro dell'interfaccia di Paint deve essere scritta come
IControl.Paint, nonITextBox.Paint.esempio finale
19.6.3 Univocità delle interfacce implementate
Le interfacce implementate da una dichiarazione di tipo generico rimarranno univoche per tutti i possibili tipi costruiti. Senza questa regola, sarebbe impossibile determinare il metodo corretto da usare per certi tipi costruiti.
Esempio: si supponga che una dichiarazione di classe generica sia stata autorizzata a essere scritta nel modo seguente:
interface I<T> { void F(); } class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict { void I<U>.F() {...} void I<V>.F() {...} }Se ciò fosse consentito, sarebbe impossibile determinare quale codice eseguire nel caso seguente:
I<int> x = new X<int, int>(); x.F();esempio finale
Per determinare se l'elenco di interfacce di una dichiarazione di tipo generico è valido, vengono eseguiti i passaggi seguenti:
- Si supponga di
Lessere l'elenco di interfacce specificate direttamente in una classe, uno struct o una dichiarazione di interfaccia genericaC. - Aggiungere a
Lqualsiasi interfaccia di base delle interfacce già inL. - Rimuovere eventuali duplicati da
L. - Se qualsiasi tipo costruito creato da
Cfosse, dopo che gli argomenti di tipo vengono sostituiti inL, causasse che due interfacce inLfossero identiche, allora la dichiarazione diCnon è valida. Le dichiarazioni di vincolo non vengono considerate quando si determinano tutti i tipi costruiti possibili.
Nota: Nella dichiarazione di classe
Xsopra, l'elenco delle interfacceLconsiste dil<U>eI<V>. La dichiarazione non è valida perché qualsiasi tipo costruito conUeVcon lo stesso tipo causerebbe che queste due interfacce siano tipi identici. nota finale
È possibile che le interfacce specificate a diversi livelli di ereditarietà unificano:
interface I<T>
{
void F();
}
class Base<U> : I<U>
{
void I<U>.F() {...}
}
class Derived<U, V> : Base<U>, I<V> // Ok
{
void I<V>.F() {...}
}
Questo codice è valido anche se Derived<U,V> implementa sia I<U> che I<V>. Codice
I<int> x = new Derived<int, int>();
x.F();
richiama il metodo in Derived, poiché Derived<int,int>' implementa I<int> di nuovo in modo efficace (§19.6.7).
19.6.4 Implementazione di metodi generici
Quando un metodo generico implementa in modo implicito un metodo di interfaccia, i vincoli specificati per ogni parametro di tipo di metodo devono essere equivalenti in entrambe le dichiarazioni (dopo che tutti i parametri del tipo di interfaccia vengono sostituiti con gli argomenti di tipo appropriati), dove i parametri del tipo di metodo vengono identificati dalle posizioni ordinali, da sinistra a destra.
Esempio: nel codice seguente:
interface I<X, Y, Z> { void F<T>(T t) where T : X; void G<T>(T t) where T : Y; void H<T>(T t) where T : Z; } class C : I<object, C, string> { public void F<T>(T t) {...} // Ok public void G<T>(T t) where T : C {...} // Ok public void H<T>(T t) where T : string {...} // Error }Il metodo
C.F<T>implementaI<object,C,string>.F<T>in modo implicito . In questo caso,C.F<T>non è obbligatorio (né consentito) specificare il vincoloT: objectperchéobjectè un vincolo implicito per tutti i parametri di tipo. Il metodoC.G<T>implementaI<object,C,string>.G<T>in modo implicito perché i vincoli corrispondono a quelli nell'interfaccia, dopo che i parametri del tipo di interfaccia vengono sostituiti con gli argomenti di tipo corrispondenti. Il vincolo per il metodoC.H<T>è un errore perché i tipi sealed (stringin questo caso ) non possono essere usati come vincoli. Omettere il vincolo sarebbe anche un errore, poiché i vincoli delle implementazioni implicite dei metodi di interfaccia devono corrispondere. Pertanto, è impossibile implementareI<object,C,string>.H<T>in modo implicito . Questo metodo di interfaccia può essere implementato solo usando un'implementazione esplicita del membro dell'interfaccia:class C : I<object, C, string> { ... public void H<U>(U u) where U : class {...} void I<object, C, string>.H<T>(T t) { string s = t; // Ok H<T>(t); } }In questo caso, l'implementazione esplicita del membro dell'interfaccia richiama un metodo pubblico con vincoli strettamente più deboli. L'assegnazione da t a s è valida perché
Teredita un vincolo diT: string, anche se questo vincolo non è expressible nel codice sorgente. esempio finale
Nota: quando un metodo generico implementa in modo esplicito un metodo di interfaccia, non sono consentiti vincoli sul metodo di implementazione (§15.7.1, §19.6.2). nota finale
Mapping dell'interfaccia 19.6.5
Una classe o uno struct fornisce implementazioni di tutti i membri astratti delle interfacce elencate nell'elenco di classi base della classe o dello struct. Il processo di individuazione delle implementazioni dei membri dell'interfaccia in una classe o uno struct di implementazione è noto come mapping dell'interfaccia.
Il mapping dell'interfaccia per una classe o uno struct C individua un'implementazione per ogni membro di ogni interfaccia specificata nell'elenco di classi base di C. L'implementazione di un particolare membro I.Mdell'interfaccia , dove I è l'interfaccia in cui viene dichiarato il membro M , viene determinata esaminando ogni classe, interfaccia o struct S, a partire da C e ripetendo per ogni classe base successiva e implementata l'interfaccia di C, fino a quando non si trova una corrispondenza:
- Se
Scontiene una dichiarazione di un'implementazione esplicita del membro dell'interfaccia corrispondenteIa eM, questo membro è l'implementazione diI.M. - In caso contrario, se
Scontiene una dichiarazione di un membro pubblico non statico che corrispondeMa , questo membro è l'implementazione diI.M. Se più di un membro corrisponde, non è specificato quale membro sia l'implementazione diI.M. Questa situazione può verificarsi solo seSè un tipo costruito in cui i due membri dichiarati nel tipo generico hanno firme diverse, ma gli argomenti di tipo rendono identiche le firme.
Si verifica un errore in fase di compilazione se le implementazioni non possono trovarsi per tutti i membri di tutte le interfacce specificate nell'elenco di classi di base di C. I membri di un'interfaccia includono i membri ereditati dalle interfacce di base.
I membri di un tipo di interfaccia costruito sono considerati come se i loro parametri di tipo fossero sostituiti con gli argomenti di tipo corrispondenti, come specificato in §15.3.3.
Esempio: ad esempio, data la dichiarazione di interfaccia generica:
interface I<T> { T F(int x, T[,] y); T this[int y] { get; } }L'interfaccia
I<string[]>costruita ha i membri:string[] F(int x, string[,][] y); string[] this[int y] { get; }esempio finale
Ai fini del mapping dell'interfaccia, una classe, un'interfaccia o un membro A struct corrisponde a un membro B dell'interfaccia quando:
-
AeBsono metodi e il nome, il tipo e gli elenchi di parametri diAeBsono identici. -
AeBsono proprietà, il nome e il tipo diAeBsono identici eAhanno le stesse funzioni di accesso diB(Aè consentito avere funzioni di accesso aggiuntive se non è un'implementazione esplicita del membro dell'interfaccia). -
AeBsono eventi e il nome e il tipo diAeBsono identici. -
AeBsono indicizzatori, gli elenchi di tipi e parametri diAeBsono identici eAhanno le stesse funzioni di accesso diB(Aè consentito avere funzioni di accesso aggiuntive se non è un'implementazione esplicita del membro dell'interfaccia).
Le implicazioni rilevanti dell'algoritmo di mapping dell'interfaccia sono:
- Le implementazioni esplicite dei membri dell'interfaccia hanno la precedenza su altri membri nella stessa classe o struct quando si determina la classe o il membro struct che implementa un membro dell'interfaccia.
- Né i membri non pubblici né statici partecipano al mapping dell'interfaccia.
Esempio: nel codice seguente
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }il membro
ICloneable.ClonediCdiventa l'implementazione diCloneinICloneableperché le implementazioni esplicite dei membri dell'interfaccia hanno la precedenza su altri membri.esempio finale
Se una classe o uno struct implementa due o più interfacce contenenti un membro con lo stesso nome, tipo e tipo di parametro, è possibile eseguire il mapping di ognuno di questi membri di interfaccia a un singolo membro di classe o struct.
Esempio:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }In questo caso, i
Paintmetodi diIControleIFormvengono mappati alPaintmetodo inPage. È naturalmente anche possibile avere implementazioni separate dei membri di interfaccia esplicite per i due metodi.esempio finale
Se una classe o uno struct implementa un'interfaccia contenente membri nascosti, potrebbe essere necessario implementare alcuni membri tramite implementazioni esplicite dei membri dell'interfaccia.
Esempio:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }Un'implementazione di questa interfaccia richiederebbe almeno un'implementazione esplicita dei membri dell'interfaccia e avrebbe una delle forme seguenti
class C1 : IDerived { int IBase.P { get; } int IDerived.P() {...} } class C2 : IDerived { public int P { get; } int IDerived.P() {...} } class C3 : IDerived { int IBase.P { get; } public int P() {...} }esempio finale
Quando una classe implementa più interfacce con la stessa interfaccia di base, può essere presente una sola implementazione dell'interfaccia di base.
Esempio: nel codice seguente
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } class ComboBox : IControl, ITextBox, IListBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} void IListBox.SetItems(string[] items) {...} }non è possibile avere implementazioni separate per
IControlelencato nella lista delle classi di base, ilIControlereditato daITextBox, e ilIControlereditato daIListBox. Infatti, non esiste alcuna nozione di identità separata per queste interfacce. Invece, le implementazioni diITextBoxeIListBoxcondividono la stessa implementazione diIControledComboBoxè semplicemente considerata l'implementazione di tre interfacce,IControl,ITextBoxeIListBox.esempio finale
I membri di una classe base partecipano al mapping dell'interfaccia.
Esempio: nel codice seguente
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }il metodo
FinClass1viene usato nell'implementazioneClass2'sdiInterface1.esempio finale
19.6.6 Ereditarietà dell'implementazione dell'interfaccia
Una classe eredita tutte le implementazioni dell'interfaccia fornite dalle relative classi di base.
Senza implementare in modo esplicito un'interfaccia, una classe derivata non può modificare in alcun modo i mapping dell'interfaccia che eredita dalle relative classi di base.
Esempio: nelle dichiarazioni
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }Il
Paintmetodo inTextBoxnasconde ilPaintmetodo inControl, ma non modifica il mapping diControl.PaintsuIControl.Paint, e le chiamate aPainttramite istanze di classe e istanze di interfaccia avranno gli effetti seguentiControl c = new Control(); TextBox t = new TextBox(); IControl ic = c; IControl it = t; c.Paint(); // invokes Control.Paint(); t.Paint(); // invokes TextBox.Paint(); ic.Paint(); // invokes Control.Paint(); it.Paint(); // invokes Control.Paint();esempio finale
Tuttavia, quando un metodo di interfaccia viene mappato a un metodo virtuale in una classe, è possibile che le classi derivate eseseguono l'override del metodo virtuale e modifichino l'implementazione dell'interfaccia.
Esempio: riscrittura delle dichiarazioni precedenti in
interface IControl { void Paint(); } class Control : IControl { public virtual void Paint() {...} } class TextBox : Control { public override void Paint() {...} }gli effetti seguenti saranno ora osservati
Control c = new Control(); TextBox t = new TextBox(); IControl ic = c; IControl it = t; c.Paint(); // invokes Control.Paint(); t.Paint(); // invokes TextBox.Paint(); ic.Paint(); // invokes Control.Paint(); it.Paint(); // invokes TextBox.Paint();esempio finale
Poiché le implementazioni esplicite dei membri dell'interfaccia non possono essere dichiarate virtuali, non è possibile eseguire l'override di un'implementazione esplicita del membro dell'interfaccia. Tuttavia, è perfettamente valido per un'implementazione esplicita del membro dell'interfaccia per chiamare un altro metodo e che un altro metodo può essere dichiarato virtuale per consentire alle classi derivate di eseguirne l'override.
Esempio:
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }In questo caso, le classi derivate da
Controlpossono specializzare l'implementazione diIControl.Painteseguendo l'override delPaintControlmetodo .esempio finale
19.6.7 Implementazione dell'interfaccia
Una classe che eredita un'implementazione dell'interfaccia è autorizzata a implementare nuovamente l'interfaccia includendola nell'elenco di classi di base.
Una ri-implementazione di un'interfaccia segue esattamente le stesse regole di mapping dell'interfaccia di un'implementazione iniziale di un'interfaccia. Di conseguenza, il mapping dell'interfaccia ereditato non ha alcun effetto sul mapping dell'interfaccia stabilito per la ri-implementazione dell'interfaccia.
Esempio: nelle dichiarazioni
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }il fatto che
ControlmappaIControl.PaintsuControl.IControl.Paintnon influisce sulla reimplementazione inMyControl, che mappaIControl.PaintsuMyControl.Paint.esempio finale
Le dichiarazioni dei membri pubblici ereditate e le dichiarazioni di membri di interfaccia esplicite ereditate partecipano al processo di mapping dell'interfaccia per le interfacce implementate di nuovo.
Esempio:
interface IMethods { void F(); void G(); void H(); void I(); } class Base : IMethods { void IMethods.F() {} void IMethods.G() {} public void H() {} public void I() {} } class Derived : Base, IMethods { public void F() {} void IMethods.H() {} }In questo caso, l'implementazione di
IMethodsinDerivedesegue il mapping dei metodi dell'interfaccia suDerived.F,Base.IMethods.G,Derived.IMethods.HeBase.I.esempio finale
Quando una classe implementa un'interfaccia, implementa in modo implicito anche tutte le interfacce di base dell'interfaccia. Analogamente, anche una ri-implementazione di un'interfaccia è implicitamente una ri-implementazione di tutte le interfacce di base dell'interfaccia.
Esempio:
interface IBase { void F(); } interface IDerived : IBase { void G(); } class C : IDerived { void IBase.F() {...} void IDerived.G() {...} } class D : C, IDerived { public void F() {...} public void G() {...} }In questo caso, la ri-implementazione di
IDerivedimplementa ancheIBase, eseguendo il mapping diIBase.FsuD.F.esempio finale
19.6.8 Classi e interfacce astratte
Analogamente a una classe non astratta, una classe astratta fornisce implementazioni di tutti i membri astratti delle interfacce elencate nell'elenco di classi di base della classe. Tuttavia, una classe astratta è autorizzata a eseguire il mapping dei metodi di interfaccia a metodi astratti.
Esempio:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }In questo caso, l'implementazione delle
IMethodsmappeFeGdei metodi astratti, che devono essere sottoposti a override in classi non astratte che derivano daC.esempio finale
Le implementazioni esplicite dei membri dell'interfaccia non possono essere astratte, ma le implementazioni esplicite dei membri dell'interfaccia sono naturalmente autorizzate a chiamare metodi astratti.
Esempio:
interface IMethods { void F(); void G(); } abstract class C: IMethods { void IMethods.F() { FF(); } void IMethods.G() { GG(); } protected abstract void FF(); protected abstract void GG(); }In questo caso, le classi non astratte che derivano da
Cdovrebbero eseguire l'override diFFeGG, fornendo quindi l'implementazione effettiva diIMethods.esempio finale
ECMA C# draft specification