Condividi tramite


18 interfacce

18.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 metodi, proprietà, eventi e indicizzatori. L'interfaccia stessa non fornisce implementazioni per i membri dichiarati. L'interfaccia specifica semplicemente i membri che devono essere forniti da classi o struct che implementano l'interfaccia.

18.2 Dichiarazioni di interfaccia

18.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 (§22), seguito da un set facoltativo di interface_modifiers (§18.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 di variant_type_parameter_list facoltativa (§18.2.3), seguita da un interface_base facoltativo specifica (§18.2.4), seguita da una specifica facoltativa type_parameter_constraints_clause(§15.2.5), seguita da un interface_body (§18.3), seguita facoltativamente da un punto e virgola.

Una dichiarazione di interfaccia non fornisce un 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).

18.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 (§23.2) è disponibile solo nel codice non sicuro (§23).

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 modificatori , publicprotected, e internal ), si applicano le regole in private.

18.2.3 Elenchi di parametri di tipo Variant

18.2.3.1 Generale

Gli elenchi di parametri di tipo varianti possono verificarsi solo nei tipi di interfaccia e delegato. La differenza rispetto ai type_parameter_listordinari è la variance_annotation facoltativa per ogni parametro di tipo.

variant_type_parameter_list
    : '<' variant_type_parameters '>'
    ;
variant_type_parameters
    : attributes? variance_annotation? type_parameter
    | variant_type_parameters ',' 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.

18.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 è unsafe di output se uno dei blocchi seguenti:

  • T è un parametro di tipo controvariante
  • T è un tipo di matrice con un tipo di elemento output-unsafe
  • T è un tipo Sᵢ,... Aₑ di interfaccia o delegato costruito da un tipo S<Xᵢ, ... Xₑ> generico in cui per almeno uno Aᵢ dei blocchi seguenti:
    • Xᵢ è covariante o invariante ed Aᵢ è output-unsafe.
    • Xᵢ è controvariante o invariante ed Aᵢ è input-unsafe.

Un tipo T è unsafe di input se uno dei blocchi seguenti:

  • T è un parametro di tipo covariante
  • T è un tipo di matrice con un tipo di elemento input-unsafe
  • T è un tipo S<Aᵢ,... Aₑ> di interfaccia o delegato costruito da un tipo S<Xᵢ, ... Xₑ> generico in cui per almeno uno Aᵢ dei blocchi seguenti:
    • Xᵢ è covariante o invariante ed Aᵢ è un input-unsafe.
    • 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 è indipendente dall'output se non è unsafe per l'output e non è sicuro per l'input se non è unsafe di input.

18.2.3.3 Conversione della varianza

Lo scopo delle annotazioni di varianza è fornire conversioni più lenienti (ma ancora indipendenti dai tipi) 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 in varianza in un tipo T<Bᵢ, ..., Bᵥ> se T è un'interfaccia o un tipo delegato dichiarato con i T<Xᵢ, ..., Xᵥ>parametri di tipo variant e per ogni parametro Xᵢ di tipo variant uno dei blocchi seguenti:

  • 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ᵢ

Interfacce di base 18.2.4

Un'interfaccia può ereditare da zero o più tipi di interfaccia, denominati interfacce di base esplicite dell'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.

interface_base
    : ':' interface_type_list
    ;

Le interfacce di base esplicite possono essere costruite tipi di interfaccia (§8.4, §18.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 se stesso.

Le interfacce di base di un'interfaccia sono 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 (§18.2.3.2).

18.3 Corpo dell'interfaccia

Il interface_body di un'interfaccia definisce i membri dell'interfaccia.

interface_body
    : '{' interface_member_declaration* '}'
    ;

18.4 Membri dell'interfaccia

18.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
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

Una dichiarazione di interfaccia dichiara zero o più membri. I membri di un'interfaccia devono essere metodi, proprietà, eventi o indicizzatori. Un'interfaccia non può contenere costanti, campi, operatori, costruttori di istanze, finalizzatori o tipi, né può contenere membri statici di qualsiasi tipo.

Tutti i membri dell'interfaccia dispongono implicitamente dell'accesso pubblico. Si tratta di un errore in fase di compilazione per le dichiarazioni dei membri dell'interfaccia per includere eventuali modificatori.

Un interface_declaration crea un nuovo spazio di dichiarazione (§7.3) e i parametri di tipo e interface_member_declarationimmediatamente contenuti dal interface_declaration introducono nuovi membri nello spazio di dichiarazione. Le regole seguenti si applicano a interface_member_declarations:

  • Il nome di un parametro di tipo nella variant_type_parameter_list di una dichiarazione di interfaccia deve essere diverso dai nomi di tutti gli altri parametri di tipo nella stessa variant_type_parameter_list e deve essere diverso dai nomi di tutti i membri dell'interfaccia.
  • Il nome di un metodo deve essere diverso dai nomi di tutte le proprietà ed eventi dichiarati nella stessa interfaccia. Inoltre, la firma (§7.6) di un metodo è diversa dalle firme di tutti gli altri metodi dichiarati nella stessa interfaccia e due metodi dichiarati nella stessa interfaccia non devono avere firme che differiscono esclusivamente per in, oute ref.
  • Il nome di una proprietà o di un evento deve essere diverso dai nomi di tutti gli altri membri dichiarati nella stessa interfaccia.
  • La firma di un indicizzatore deve essere diversa dalle firme di tutti gli altri indicizzatori dichiarati nella stessa interfaccia.

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, il membro dell'interfaccia derivata viene detto nascondere il membro dell'interfaccia di base. Nascondere un membro ereditato non è considerato un errore, ma fa sì che un compilatore genera un avviso. Per eliminare l'avviso, la dichiarazione del membro dell'interfaccia derivata deve includere un new modificatore per indicare che il membro derivato deve nascondere il membro di base. Questo argomento è illustrato più avanti in §7.7.2.3.

Se un new modificatore viene incluso in una dichiarazione che non nasconde un membro ereditato, viene generato un avviso a tale effetto. Questo avviso viene eliminato rimuovendo il new modificatore.

Nota: i membri della classe object non sono, in senso stretto, membri di qualsiasi interfaccia (§18.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.

18.4.2 Metodi di interfaccia

I metodi di interfaccia vengono dichiarati usando interface_method_declarations:

interface_method_declaration
    : attributes? 'new'? return_type interface_method_header
    | attributes? 'new'? ref_kind ref_return_type interface_method_header
    ;

interface_method_header
    : identifier '(' parameter_list? ')' ';'
    | identifier type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause* ';'
    ;

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 (§15.6). Una dichiarazione di metodo di interfaccia non è consentita per specificare un corpo del metodo e la dichiarazione termina quindi sempre con un punto e virgola.

Tutti i tipi di parametro di un metodo di interfaccia devono essere indipendenti dall'input (§18.2.3.2) e il tipo restituito deve essere void o indipendente dall'output. Inoltre, 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

Inoltre, ogni vincolo di tipo di classe, vincolo del tipo di interfaccia e vincolo di parametro di tipo su qualsiasi parametro di tipo del metodo deve essere indipendente dall'input.

Inoltre, ogni vincolo di tipo di classe, vincolo del tipo di interfaccia e vincolo di parametro di tipo su 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 come vincolo di parametro di T tipo su U non è sicuro per l'input.

Se questa restrizione non fosse in vigore, sarebbe possibile violare la sicurezza dei tipi nel modo seguente:

class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<U>() {...} 
}

...

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

18.4.3 Proprietà dell'interfaccia

Le proprietà dell'interfaccia vengono dichiarate usando interface_property_declarations:

interface_property_declaration
    : attributes? 'new'? type identifier '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
    ;

interface_accessors
    : attributes? 'get' ';'
    | attributes? 'set' ';'
    | attributes? 'get' ';' attributes? 'set' ';'
    | attributes? 'set' ';' attributes? 'get' ';'
    ;

ref_interface_accessor
    : attributes? 'get' ';'
    ;

Gli attributi, il tipo e l'identificatore di una dichiarazione di proprietà di interfaccia hanno lo stesso significato di quelli di una dichiarazione di proprietà in una classe (§15.7).

Le funzioni di accesso di una dichiarazione di proprietà di interfaccia corrispondono alle funzioni di accesso di una dichiarazione di proprietà di classe (§15.7.3), ad eccezione del fatto che il accessor_body deve essere sempre un punto e virgola. Pertanto, le funzioni di accesso indicano semplicemente se la proprietà è di lettura/scrittura, di sola lettura o di sola scrittura.

Il tipo di una proprietà di interfaccia deve essere indipendente dall'output se è presente una funzione di accesso get e deve essere indipendente dall'input se è presente una funzione di accesso set.

18.4.4 Eventi dell'interfaccia

Gli eventi di interfaccia vengono dichiarati usando interface_event_declarations:

interface_event_declaration
    : attributes? 'new'? 'event' type identifier ';'
    ;

Gli attributi, il tipo e l'identificatore di una dichiarazione di evento di interfaccia hanno lo stesso significato di quelli di una dichiarazione di evento in una classe (§15.8).

Il tipo di un evento di interfaccia deve essere indipendente dall'input.

18.4.5 Indicizzatori di interfaccia

Gli indicizzatori di interfaccia vengono dichiarati usando interface_indexer_declarations:

interface_indexer_declaration
    : attributes? 'new'? type 'this' '[' parameter_list ']'
      '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
      '{' ref_interface_accessor '}'
    ;

Gli attributi, il tipo e parameter_list di una dichiarazione dell'indicizzatore di interfaccia hanno lo stesso significato di quelli di una dichiarazione dell'indicizzatore in una classe (§15.9).

Le funzioni di accesso di una dichiarazione dell'indicizzatore di interfaccia corrispondono alle funzioni di accesso di una dichiarazione dell'indicizzatore di classe (§15.9), ad eccezione del fatto che il accessor_body deve essere sempre un punto e virgola. Pertanto, le funzioni di accesso indicano semplicemente se l'indicizzatore è di sola lettura, di sola lettura o di sola scrittura.

Tutti i tipi di parametro di un indicizzatore di interfaccia devono essere indipendenti dall'input (§18.2.3.2). Inoltre, 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 indipendente dall'output se è presente una funzione di accesso get e deve essere indipendente dall'input se è presente una funzione di accesso set.

18.4.6 Accesso ai membri dell'interfaccia

I membri dell'interfaccia sono acceduti tramite l'accesso ai membri (§12.8.7) e l'accesso dell'indicizzatore (§12.8.12.3) nelle espressioni di forma I.M e I[A], dove I è un tipo di interfaccia, M è un metodo, una proprietà o un evento di tale tipo di interfaccia, e A è un elenco di argomenti dell'indicizzatore.

Per le interfacce a ereditarietà strettamente singola (ogni interfaccia della catena di ereditarietà ha esattamente zero o un'interfaccia di base diretta), gli effetti della ricerca membro (§12.5), chiamata al metodo (§12.8.10.2) e l'accesso all'indicizzatore (§12.8.12.3) sono esattamente uguali a quelli 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 cast espliciti per risolvere le ambiguità.

Esempio: nel codice seguente

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    void Count(int i);
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count(1);             // Error
        x.Count = 1;            // Error
        ((IList)x).Count = 1;   // Ok, invokes IList.Count.set
        ((ICounter)x).Count(1); // Ok, invokes ICounter.Count
    }
}

le prime due istruzioni causano errori in fase di compilazione perché la ricerca 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 a a nasconde , il membro viene nascosto anche nel percorso di accesso da IDerived a a ILeftIBase .IBase.FIDerivedIRightIBase

esempio finale

18.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 delle dichiarazioni

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 System
{
    public interface ICloneable
    {
        object Clone();
    }
}

All'interno dello spazio dei System nomi , sia ICloneable.Clone che System.ICloneable.Clone sono nomi di membri di interfaccia qualificati per il Clone metodo .

esempio finale

Implementazioni dell'interfaccia 18.6

18.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.

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, §18.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 §18.6.3.

18.6.2 Implementazioni esplicite dei membri dell'interfaccia

Ai fini dell'implementazione di interfacce, una classe o uno struct può 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.

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) {...}
}

Di seguito IDictionary<int,T>.this sono riportate le IDictionary<int,T>.Add 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 completo del membro dell'interfaccia in una chiamata al metodo, l'accesso alle proprietà, l'accesso agli eventi o l'accesso dell'indicizzatore. È possibile accedere a un'implementazione esplicita del membro dell'interfaccia solo tramite un'istanza dell'interfaccia ed è in questo caso fatto riferimento semplicemente dal nome del membro.

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.

Si tratta di un errore in fase di compilazione per un'implementazione esplicita del metodo di interfaccia da includere type_parameter_constraints_clauses. I vincoli per l'implementazione di un metodo di interfaccia esplicito generico vengono ereditati dal metodo di interfaccia.

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 che una classe o uno struct abbia implementazioni diverse di membri di interfaccia con la stessa firma e tipo restituito, come sarebbe impossibile per una classe o uno struct avere un'implementazione a 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 o lo struct denominano un'interfaccia nel relativo elenco di classi di base che contiene un membro il cui nome completo del membro di interfaccia, il tipo, il numero di parametri di tipo e i 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: di conseguenza, nella classe seguente

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

la dichiarazione di restituisce un errore in fase di IComparable.CompareTo compilazione 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 completo del membro dell'interfaccia di un'implementazione esplicita del membro dell'interfaccia fa riferimento all'interfaccia in cui è stato dichiarato il membro.

Esempio: di conseguenza, 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

18.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 per chiamare per determinati 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 verrebbe creato, dopo che gli argomenti di tipo vengono sostituiti in L, causa due interfacce in L essere identiche, la dichiarazione di C non è valida. Le dichiarazioni di vincolo non vengono considerate quando si determinano tutti i tipi costruiti possibili.

Nota: nella dichiarazione X di classe precedente l'elenco L di l<U> interfacce è costituito da 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 , poiché Derived implementa Derived<int,int>' di fatto (I<int>).

18.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. L'omissione del vincolo potrebbe anche essere un errore perché sono necessari vincoli di implementazioni implicite del metodo di interfaccia. 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 nel metodo di implementazione (§15.7.1, §18.6.2). nota finale

Mapping dell'interfaccia 18.6.5

Una classe o uno struct fornisce implementazioni di tutti i membri delle interfacce elencate nell'elenco di classi di 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 o struct S, a partire da C e ripetendo per ogni classe base successiva 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 corrisponde a più membri, non è specificato quale membro è 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 vengono considerati come parametri di tipo 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, un membro A di classe o struct corrisponde a un membro B di 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 l'oggetto denominato nell'elenco IControl delle classi di base, l'ereditato IControl da ITextBoxe l'oggetto 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

18.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 su Control.Painte le chiamate a IControl.Paint tramite istanze di Paint 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

18.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 esegue il mapping IControl.Paint a Control.IControl.Paint non influisce sulla ripetizione dell'implementazione in MyControl, che esegue il mapping IControl.Paint a 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 in esegue il mapping dei metodi di IMethods interfaccia a Derived, Derived.FBase.IMethods.G, e Derived.IMethods.H.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 IBaseanche , eseguendo il mapping IBase.F a D.F.

esempio finale

18.6.8 Classi e interfacce astratte

Analogamente a una classe non astratta, una classe astratta fornisce implementazioni di tutti i membri 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 sarebbero necessarie per eseguire l'override di e FF, fornendo quindi l'implementazione GG effettiva di IMethods.

esempio finale