Condividi tramite


19 interfacce

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 ed Z è 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 tipo Sᵢ,... Aₑ di interfaccia o delegato costruito da un tipo S<Xᵢ, ... Xₑ> generico in cui per almeno uno dei seguenti casi Aᵢ è vero:
    • Xᵢ è covariante o invariante ed Aᵢ è output-unsafe.
    • Xᵢ è controvariante o invariante ed Aᵢ è 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 tipo S<Aᵢ,... Aₑ> di interfaccia o delegato costruito da un tipo S<Xᵢ, ... Xₑ> generico in cui per almeno uno dei seguenti casi Aᵢ è vero:
    • Xᵢ è covariante o invariante e Aᵢ non è sicuro per l'input.
    • Xᵢ è controvariante o invariante ed Aᵢ è 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à da Aᵢ a Bᵢ
  • Xᵢ è controvariante e esiste una conversione implicita di riferimento o identità da Bᵢ a Aᵢ
  • Xᵢ è invariante e esiste una conversione di identità da Aᵢ a Bᵢ

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 private o internal nella 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 IComboBox sono IControl, ITextBoxe IListBox. In altre parole, l'interfaccia IComboBox precedente eredita i membri SetText e SetItemsPaint.

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 IDerived eredita il Combine metodo dopo che il parametro T di tipo viene sostituito con string[,].

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 abstract modificatore è 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 virtual implicito, a meno che non venga utilizzato il sealed modificatore o private . Il virtual modificatore può essere fornito in modo esplicito.
  • Un private membro o sealed di una funzione di un'interfaccia deve avere un corpo.
  • Un private membro della funzione non deve avere il modificatore sealed.
  • 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 object non sono, in senso stretto, membri di qualsiasi interfaccia (§19.4). Tuttavia, i membri della classe object sono 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 IA con un'implementazione per un membro M e una proprietà P. Un tipo C di implementazione non fornisce un'implementazione per M o P. È necessario accedervi tramite un riferimento il cui tipo in fase di compilazione è un'interfaccia convertibile in modo implicito in IA o IB. Questi membri non vengono trovati tramite la ricerca dei membri in una variabile di tipo C.

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 IA e IB, il membro M è accessibile direttamente in base al nome. Tuttavia, all'interno del metodo Main, non è possibile scrivere c.M() o c.P, perché tali nomi non sono visibili. Per trovarli, è necessario eseguire il cast al tipo di interfaccia appropriato. La dichiarazione di M in IB usa la sintassi esplicita di implementazione dell'interfaccia. Ciò è necessario per eseguire l'override di tale metodo in IA. Il modificatore override potrebbe 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 = 50

esempio 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; il abstract modificatore non è obbligatorio, ma è consentito.

  • Una dichiarazione del metodo di interfaccia con corpo di blocco o corpo dell'espressione come method_body è virtual; il virtual modificatore 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 void o 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 T come vincolo del parametro di tipo su U non è 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 che E derivi da D, 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 Main e 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; il virtual modificatore non è obbligatorio, ma è consentito.

  • Un'istanza property_declaration senza implementazione è abstract; il abstract modificatore 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 virtual modificatori o sealed deve dichiarare le funzioni di accesso. Non è mai considerato un evento di tipo campo implementato automaticamente (§15.8.2).
  • Un evento di istanza con il abstract modificatore 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; il virtual modificatore non è obbligatorio, ma è consentito.

  • Un indexer_declaration i cui corpi delle funzioni di accesso sono punti e virgola (;) è abstract; il abstract modificatore 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 Main metodo venga chiamato per un'interfaccia contenente il Main metodo (§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 C seguito è 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 T dichiara un'implementazione di I.M, tale implementazione è l'implementazione più specifica.
  • In caso contrario, se T è una classe e una classe base diretta o indiretta dichiara un'implementazione di I.M, la classe base più derivata di T è l'implementazione più specifica.
  • In caso contrario, se I2 e I3 sono interfacce implementate da T e I3 derivano direttamente I2 o indirettamente, I3.M è un'implementazione più specifica di I2.M.
  • In caso contrario, né I2.MI3.M sono 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 Count in IListCounter è ambigua. Come illustrato nell'esempio, l'ambiguità viene risolta eseguendo il cast x al 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) seleziona IInteger.Add applicando le regole di risoluzione dell'overload di §12.6.4. Analogamente, la chiamata n.Add(1.0) seleziona IDouble.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.F membro è nascosto dal ILeft.F membro. La chiamata d.F(1) seleziona ILeft.Fquindi , anche se IBase.F sembra non essere nascosta nel percorso di accesso che conduce attraverso IRight.

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 IDerived a ILeft a IBase nasconde IBase.F, il membro viene nascosto anche nel percorso di accesso da IDerived a IRight a IBase.

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.Paint e il nome completo di SetText è ITextBox.SetText. Nell'esempio precedente non è possibile fare riferimento a Paint .ITextBox.Paint

esempio 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, sia IPolygon.CalculateArea che GraphicsLib.IPolygon.CalculateArea sono nomi di membri di interfaccia qualificati per il metodo CalculateArea.

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 TextBox implementa sia IControl che ITextBox.

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>.this e IDictionary<int,T>.Add sono 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 Close funzione membro che ha l'effetto di rilasciare la risorsa file e implementare il Dispose metodo dell'interfaccia usando l'implementazione esplicita del membro dell'interfaccia IDisposable :

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 classT parametro 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 tipo T è un tipo di valore nullable T?.

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.CompareTo restituisce un errore di compilazione in fase di perché IComparable non è elencata nell'elenco di classi di base di Shape e non è un'interfaccia di base di ICloneable. Analogamente, nelle dichiarazioni

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

la dichiarazione di ICloneable.CloneEllipse in genera un errore in fase di compilazione perché ICloneable non è elencata in modo esplicito nell'elenco di classi di base di Ellipse.

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, non ITextBox.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 L essere l'elenco di interfacce specificate direttamente in una classe, uno struct o una dichiarazione di interfaccia generica C.
  • Aggiungere a L qualsiasi interfaccia di base delle interfacce già in L.
  • Rimuovere eventuali duplicati da L.
  • Se qualsiasi tipo costruito creato da C fosse, dopo che gli argomenti di tipo vengono sostituiti in L, causasse che due interfacce in L fossero identiche, allora la dichiarazione di C non è valida. Le dichiarazioni di vincolo non vengono considerate quando si determinano tutti i tipi costruiti possibili.

Nota: Nella dichiarazione di classe X sopra, l'elenco delle interfacce L consiste di l<U> e I<V>. La dichiarazione non è valida perché qualsiasi tipo costruito con U e V con 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> implementa I<object,C,string>.F<T>in modo implicito . In questo caso, C.F<T> non è obbligatorio (né consentito) specificare il vincolo T: object perché object è un vincolo implicito per tutti i parametri di tipo. Il metodo C.G<T> implementa I<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 metodo C.H<T> è un errore perché i tipi sealed (string in 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 implementare I<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é T eredita un vincolo di T: 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 S contiene una dichiarazione di un'implementazione esplicita del membro dell'interfaccia corrispondente I a e M, questo membro è l'implementazione di I.M.
  • In caso contrario, se S contiene una dichiarazione di un membro pubblico non statico che corrisponde Ma , questo membro è l'implementazione di I.M. Se più di un membro corrisponde, non è specificato quale membro sia l'implementazione di I.M. Questa situazione può verificarsi solo se S è 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:

  • A e B sono metodi e il nome, il tipo e gli elenchi di parametri di A e B sono identici.
  • A e B sono proprietà, il nome e il tipo di A e B sono identici e A hanno le stesse funzioni di accesso di B (A è consentito avere funzioni di accesso aggiuntive se non è un'implementazione esplicita del membro dell'interfaccia).
  • A e B sono eventi e il nome e il tipo di A e B sono identici.
  • A e B sono indicizzatori, gli elenchi di tipi e parametri di A e B sono identici e A hanno le stesse funzioni di accesso di B (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.Clone di C diventa l'implementazione di Clone in ICloneable perché 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 Paint metodi di IControl e IForm vengono mappati al Paint metodo in Page. È 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 IControl elencato nella lista delle classi di base, il IControl ereditato da ITextBox, e il IControl ereditato da IListBox. Infatti, non esiste alcuna nozione di identità separata per queste interfacce. Invece, le implementazioni di ITextBoxe IListBox condividono la stessa implementazione di IControled ComboBox è semplicemente considerata l'implementazione di tre interfacce, IControl, ITextBoxe IListBox.

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 F in Class1 viene usato nell'implementazione Class2's di Interface1.

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 Paint metodo in TextBox nasconde il Paint metodo in Control, ma non modifica il mapping di Control.Paint su IControl.Paint, e le chiamate a Paint tramite istanze di classe e istanze di interfaccia avranno gli effetti seguenti

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 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 Control possono specializzare l'implementazione di IControl.Paint eseguendo l'override del PaintControl metodo .

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 Control mappa IControl.Paint su Control.IControl.Paint non influisce sulla reimplementazione in MyControl, che mappa IControl.Paint su MyControl.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 IMethods in Derived esegue il mapping dei metodi dell'interfaccia su Derived.F, Base.IMethods.G, Derived.IMethods.H e Base.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 IDerived implementa anche IBase, eseguendo il mapping di IBase.F su D.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 IMethods mappe F e G dei metodi astratti, che devono essere sottoposti a override in classi non astratte che derivano da C.

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 C dovrebbero eseguire l'override di FF e GG, fornendo quindi l'implementazione effettiva di IMethods.

esempio finale