metodi di interfaccia predefiniti
Nota
Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono documentate nelle pertinenti note del language design meeting (LDM) .
Puoi trovare ulteriori informazioni sul processo di adozione delle specifiche delle funzionalità nello standard del linguaggio C# nell'articolo sulle specifiche di .
Problema del campione: https://github.com/dotnet/csharplang/issues/52
Sommario
Aggiungere il supporto per i metodi di estensione virtuale - metodi nelle interfacce con implementazioni concrete. Una classe o uno struct che implementa tale interfaccia è tenuta ad avere una singola implementazione più specifica per il metodo dell'interfaccia, implementazione più specifica di, sia essa implementata dalla classe o dallo struct, sia ereditata dalle rispettive classi o interfacce di base. I metodi di estensione virtuale consentono a un autore dell'API di aggiungere metodi a un'interfaccia nelle versioni future senza interrompere la compatibilità di origine o binaria con le implementazioni esistenti di tale interfaccia.
Questi metodi sono simili a "Metodi predefiniti" di Java.
(In base alla probabile tecnica di implementazione) questa funzionalità richiede il supporto corrispondente nel CLI/CLR. I programmi che sfruttano questa funzionalità non possono essere eseguiti nelle versioni precedenti della piattaforma.
Motivazione
Le motivazioni principali per questa funzionalità sono
- I metodi di interfaccia predefiniti consentono a un autore dell'API di aggiungere metodi a un'interfaccia nelle versioni future senza interrompere la compatibilità di origine o binaria con le implementazioni esistenti di tale interfaccia.
- La funzionalità consente a C# di interagire con le API destinate a Android (Java) e iOS (Swift), che supportano funzionalità simili.
- A quanto pare, l'aggiunta di implementazioni predefinite delle interfacce fornisce gli elementi della funzionalità di linguaggio "traits" (https://en.wikipedia.org/wiki/Trait_(computer_programming)). I tratti hanno dimostrato di essere una potente tecnica di programmazione (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf).
Progettazione dettagliata
La sintassi per un'interfaccia viene estesa per permettere
- dichiarazioni membro che dichiarano costanti, operatori, costruttori statici e tipologie annidate;
- un corpo per un metodo o un indicizzatore, una proprietà o una funzione di accesso agli eventi (ovvero un'implementazione "predefinita").
- dichiarazioni membro che dichiarano campi, metodi, proprietà, indicizzatori ed eventi statici;
- dichiarazioni membro che usano la sintassi di implementazione esplicita dell'interfaccia; e
- Modificatori di accesso espliciti (l'accesso predefinito è
public
).
I membri con corpi consentono all'interfaccia di fornire un'implementazione "predefinita" per il metodo nelle classi e negli struct che non forniscono la propria implementazione.
Le interfacce potrebbero non contenere lo stato dell'istanza. Mentre i campi statici sono ora consentiti, i campi dell'istanza non sono consentiti nelle interfacce. Le proprietà automatiche dell'istanza non sono supportate nelle interfacce, perché dichiarano in modo implicito un campo nascosto.
I metodi statici e privati consentono un utile refactoring e una migliore organizzazione del codice usato per implementare l'API pubblica dell'interfaccia.
Un override del metodo in un'interfaccia deve usare la sintassi esplicita di implementazione dell'interfaccia.
È un errore dichiarare un tipo di classe, un tipo struct o un tipo enumerativo nell'ambito di un parametro di tipo dichiarato con un variance_annotation. Ad esempio, la dichiarazione di C
seguente è un errore.
interface IOuter<out T>
{
class C { } // error: class declaration within the scope of variant type parameter 'T'
}
Metodi concreti nelle interfacce
La forma più semplice di questa funzionalità è la possibilità di dichiarare un metodo concreto in un'interfaccia, ovvero un metodo con un corpo.
interface IA
{
void M() { WriteLine("IA.M"); }
}
Una classe che implementa questa interfaccia non deve implementare il metodo concreto.
class C : IA { } // OK
IA i = new C();
i.M(); // prints "IA.M"
L'override finale per IA.M
nella classe C
è il metodo concreto M
dichiarato in IA
. Si noti che una classe non eredita i membri dalle relative interfacce; che non viene modificato da questa funzionalità:
new C().M(); // error: class 'C' does not contain a member 'M'
All'interno di un membro dell'istanza di un'interfaccia, this
ha il tipo dell'interfaccia di inclusione.
Modificatori nelle interfacce
La sintassi per un'interfaccia è allentata per consentire modificatori ai suoi membri. Di seguito sono consentiti: private
, protected
, internal
, public
, virtual
, abstract
, sealed
, static
, extern
e partial
.
Membro dell'interfaccia la cui dichiarazione include un corpo è un membro virtual
a meno che non venga utilizzato il modificatore sealed
o private
. Il modificatore virtual
può essere usato in un membro della funzione che altrimenti verrebbe virtual
in modo implicito. Analogamente, anche se abstract
è l'impostazione predefinita per i membri dell'interfaccia senza corpi, tale modificatore può essere assegnato in modo esplicito. Un membro non virtuale può essere dichiarato usando la parola chiave sealed
.
Si tratta di un errore per un private
o sealed
membro della funzione di un'interfaccia senza corpo. Un membro della funzione private
potrebbe non avere il modificatore sealed
.
I modificatori di accesso possono essere utilizzati sui membri dell'interfaccia per tutti i tipi di membri che sono consentiti. Il livello di accesso public
è l'impostazione predefinita, ma può essere assegnato in modo esplicito.
Problema aperto: È necessario specificare il significato preciso dei modificatori di accesso, come
protected
einternal
, e quali dichiarazioni li eseguono o non li eseguono l'override (in un'interfaccia derivata) o li implementano (in una classe che implementa l'interfaccia).
Le interfacce possono dichiarare membri static
, inclusi tipi annidati, metodi, indicizzatori, proprietà, eventi e costruttori statici. Il livello di accesso predefinito per tutti i membri dell'interfaccia è public
.
Le interfacce non possono dichiarare costruttori di istanza, distruttori o campi.
problema chiuso: Le dichiarazioni degli operatori devono essere consentite in un'interfaccia? Probabilmente non si tratta di operatori di conversione, ma di altri? Decision: gli operatori sono autorizzati tranne per gli operatori di conversione, uguaglianza e disuguaglianza.
Problema Chiuso: Si dovrebbe consentire
new
nelle dichiarazioni dei membri dell'interfaccia che nascondono i membri delle interfacce base? Decisione: Sì.
Problema chiuso: Attualmente non è consentito
partial
su un'interfaccia o sui relativi membri. Ciò richiederebbe una proposta separata. Decisione: Sì. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface
Implementazione esplicita nelle interfacce
Le implementazioni esplicite consentono al programmatore di fornire un'implementazione più specifica di un membro virtuale in un'interfaccia in cui il compilatore o il runtime non ne troverebbe altrimenti uno. Una dichiarazione di implementazione può esplicitamente implementare un metodo di interfaccia di base specifico qualificando la dichiarazione con il nome dell'interfaccia (in questo caso non è consentito alcun modificatore di accesso). Le implementazioni implicite non sono consentite.
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}
Le implementazioni esplicite nelle interfacce potrebbero non essere dichiarate sealed
.
I membri della funzione public virtual
in un'interfaccia possono essere implementati solo in modo esplicito in un'interfaccia derivata (qualificando il nome nella dichiarazione con il tipo di interfaccia che ha originariamente dichiarato il metodo e omettendo un modificatore di accesso). Il membro deve essere accessibile nel punto in cui viene implementato.
Riabstrazione
Un metodo virtuale (concreto) dichiarato in un'interfaccia può essere riastraccato in un'interfaccia derivata
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.
Il modificatore abstract
è necessario nella dichiarazione di IB.M
per indicare che IA.M
viene riastrattato.
Ciò è utile nelle interfacce derivate in cui l'implementazione predefinita di un metodo non è appropriata e deve essere fornita un'implementazione più appropriata implementando le classi.
Regola di implementazione più specifica
È necessario che ogni interfaccia e classe abbia un 'implementazione più specifica per ogni membro virtuale tra le implementazioni visualizzate nel tipo o nelle relative interfacce dirette e indirette. L'implementazione più specifica è un'implementazione unica che è più specifica di ogni altra implementazione. Se non è presente alcuna implementazione, il membro stesso viene considerato l'implementazione più specifica.
Un'implementazione M1
viene considerata più specifica di un altro implementazione M2
se M1
è dichiarato sul tipo T1
, M2
è dichiarato sul tipo T2
e
-
T1
contieneT2
tra le interfacce dirette o indirette oppure -
T2
è un tipo di interfaccia, maT1
non è un tipo di interfaccia.
Per esempio:
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.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.
Poiché sono supportate riabstrazioni esplicite nelle interfacce, è possibile farlo anche nelle classi
abstract class E : IA, IB, IC // ok
{
abstract void IA.M();
}
problema chiuso: è consigliabile supportare implementazioni astratte dell'interfaccia esplicite nelle classi? Decisione: NO
Inoltre, si tratta di un errore se in una dichiarazione di classe l'implementazione più specifica di un metodo di interfaccia è un'implementazione astratta dichiarata in un'interfaccia. Si tratta di una regola esistente usata con la nuova terminologia.
interface IF
{
void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'
È possibile che una proprietà virtuale dichiarata in un'interfaccia disponga di un'implementazione più specifica per la funzione di accesso get
in un'interfaccia e un'implementazione più specifica per la relativa funzione di accesso set
in un'interfaccia diversa. Si tratta di una violazione della regola di implementazione più specifica e genera un errore del compilatore.
metodi static
e private
Poiché le interfacce possono ora contenere codice eseguibile, è utile astrarre codice comune in metodi privati e statici. Ora sono consentiti nelle interfacce.
Problema chiuso: è consigliabile supportare metodi privati? È consigliabile supportare metodi statici? decisione: SÌ
Aprire problema: dovremmo consentire ai metodi di interfaccia di essere
protected
ointernal
o avere altri tipi di accesso? In tal caso, qual è la semantica? Sonovirtual
per impostazione predefinita? In tal caso, esiste un modo per renderli non virtuali?
Problema chiuso: se si supportano metodi statici, è consigliabile supportare gli operatori statici? decisione: SÌ
Invocazioni di interfaccia di base
La sintassi in questa sezione non è stata implementata. Rimane una proposta attiva.
Il codice in un tipo che deriva da un'interfaccia con un metodo predefinito può richiamare in modo esplicito l'implementazione "base" dell'interfaccia.
interface I0
{
void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
// an explicit override that invoke's a base interface's default method
void I0.M() { I2.base.M(); }
}
Un metodo di istanza (non statico) può richiamare l'implementazione di un metodo di istanza accessibile in un'interfaccia di base diretta in modo non virtuale assegnandogli la sintassi base(Type).M
. Ciò è utile quando è necessario specificare un override a causa dell'ereditarietà dei diamanti viene risolto delegando a una particolare implementazione di base.
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
override void IA.M() { WriteLine("IC.M"); }
}
class D : IA, IB, IC
{
void IA.M() { base(IB).M(); }
}
Quando si accede a un membro virtual
o abstract
usando la sintassi base(Type).M
, è necessario che Type
contenga un override più specifico per M
.
Clausole base vincolanti
Le interfacce contengono ora tipi. Questi tipi possono essere usati nella clausola di base come interfacce di base. Quando si collega una clausola di base, potrebbe essere necessario conoscere l'insieme di interfacce base per collegare quei tipi, ad esempio per individuarli e risolvere l'accesso protetto. Il significato della clausola di base di un'interfaccia è quindi definito circolarmente. Per interrompere il ciclo, si aggiunge una nuova regola del linguaggio corrispondente a una regola simile già in vigore per le classi.
Durante la determinazione del significato della interface_base di un'interfaccia, si presuppone che le interfacce di base siano temporaneamente vuote. In modo intuitivo, ciò garantisce che il significato di una clausola di base non possa dipendere in modo ricorsivo da se stesso.
Abbiamo usato le regole seguenti:
"Quando una classe B deriva da una classe A, si tratta di un errore in fase di compilazione per A da dipendere da B. Una classe dipende direttamente da classe base diretta (se presente) e dipende direttamente da la classe all'interno della quale viene annidata immediatamente (se presente). Data questa definizione, il set completo di classi su cui una classe dipende è la chiusura riflessiva e transitiva della relazione di dipendenza diretta .
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.
Li stiamo modificando nel modo seguente:
Quando una classe B deriva da una classe A, si tratta di un errore in fase di compilazione per A da dipendere da B. Una classe dipende direttamente da classe base diretta (se presente) e dipende direttamente da il tipo di all'interno del quale viene immediatamente annidato (se presente).
Quando un'interfaccia IB estende un'interfaccia IA, è un errore in fase di compilazione se IA dipende da IB. Un'interfaccia dipende direttamente da interfacce di base dirette (se presenti) e dipende direttamente da il tipo in cui è immediatamente annidato (se presente).
Date queste definizioni, l'insieme completo di tipi e da cui un tipo dipende è la chiusura riflessiva e transitiva della relazione di dipendenza diretta .
Effetto sui programmi esistenti
Le regole presentate qui sono destinate a non avere alcun effetto sul significato dei programmi esistenti.
Esempio 1:
interface IA
{
void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
public static void M() { } // method unrelated to 'IA.M' because static
}
Esempio 2:
interface IA
{
void M();
}
class Base: IA
{
void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
Le stesse regole offrono risultati simili alla situazione analoga che prevede metodi di interfaccia predefiniti:
interface IA
{
void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
Problema chiuso: verificare che si tratta di una conseguenza prevista della specifica. decisione: SÌ
Risoluzione del metodo di runtime
Problema chiuso: La specifica deve descrivere l'algoritmo di risoluzione dei metodi di runtime in caso di metodi predefiniti dell'interfaccia. È necessario assicurarsi che la semantica sia coerente con la semantica del linguaggio, ad esempio quali metodi dichiarati eseguono o non eseguono l'override o implementano un metodo
internal
.
API di supporto CLR
Per consentire ai compilatori di rilevare quando vengono compilati per un runtime che supporta questa funzionalità, le librerie per tali runtime vengono modificate per annunciare tale fatto tramite l'API descritta in https://github.com/dotnet/corefx/issues/17116. Aggiungiamo
namespace System.Runtime.CompilerServices
{
public static class RuntimeFeature
{
// Presence of the field indicates runtime support
public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
}
}
Problema aperto: è il nome migliore per la funzionalità del CLR? La funzionalità CLR fa molto di più rispetto a quella (ad esempio, riduce i vincoli di protezione, supporta le sostituzioni nelle interfacce e così via). Forse dovrebbe essere chiamato qualcosa come "metodi concreti nelle interfacce" o "tratti"?
Ulteriori aree da specificare
- [ ] Sarebbe utile catalogare i tipi di effetti di compatibilità di origine e binari causati dall'aggiunta di metodi di interfaccia predefiniti ed override alle interfacce esistenti.
Svantaggi
Questa proposta richiede un aggiornamento coordinato alla specifica CLR (per supportare metodi concreti nelle interfacce e nella risoluzione dei metodi). È quindi piuttosto "costoso" e che potrebbe essere opportuno da combinare con altre funzionalità che prevediamo richiederanno anch'esse modifiche CLR.
Alternative
Nessuno.
Domande non risolte
- Le domande aperte sono segnalate sopra, in tutta la proposta.
- Vedere anche https://github.com/dotnet/csharplang/issues/406 per un elenco di domande aperte.
- La specifica dettagliata deve descrivere il meccanismo di risoluzione usato in fase di esecuzione per selezionare il metodo preciso da richiamare.
- L'interazione dei metadati prodotti dai nuovi compilatori e utilizzata dai compilatori meno recenti deve essere elaborata in dettaglio. Ad esempio, è necessario assicurarsi che la rappresentazione dei metadati usata non causi l'aggiunta di un'implementazione predefinita in un'interfaccia per interrompere una classe esistente che implementa tale interfaccia quando viene compilata da un compilatore precedente. Ciò può influire sulla rappresentazione dei metadati che è possibile usare.
- La progettazione deve considerare l'interoperabilità con altri linguaggi e compilatori esistenti per altri linguaggi.
Domande risolte
Sovrascrittura astratta
La bozza precedente della specifica conteneva la possibilità di "riastrarre" un metodo ereditato.
interface IA
{
void M();
}
interface IB : IA
{
override void M() { }
}
interface IC : IB
{
override void M(); // make it abstract again
}
Le mie note per il 2017-03-20 hanno dimostrato che abbiamo deciso di non consentire questo. Esistono tuttavia almeno due casi d'uso:
- Le API Java, con cui alcuni utenti di questa funzionalità sperano di interagire, dipendono da questa funzionalità.
- La programmazione con i tratti e trae vantaggio da questo. Reabstraction è uno degli elementi della caratteristica del linguaggio "traits" (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Di seguito è consentito con le classi:
public abstract class Base
{
public abstract void M();
}
public abstract class A : Base
{
public override void M() { }
}
public abstract class B : A
{
public override abstract void M(); // reabstract Base.M
}
Purtroppo, questo codice non può essere ristrutturato come un set di interfacce (traits) a meno che non sia permesso. Secondo il principio di avidità di Jared , dovrebbe essere consentito.
Problema chiuso: Dovrebbe essere consentita la ri-astrazione? [SÌ] Le mie note erano sbagliate. Le note LDM indicano che la re-astrazione è consentita in un'interfaccia. Non in una classe.
Modificatore virtuale contro modificatore sealed
Da Aleksey Tsingauz:
Abbiamo deciso di permettere modificatori indicati esplicitamente sui membri dell'interfaccia, a meno che non vi sia un motivo per non permettere alcuni. Questo porta una domanda interessante sul modificatore virtuale. Deve essere necessario per i membri con implementazione predefinita?
Potremmo dire che:
- se non è presente alcuna implementazione e non sono specificati né sealed né virtuale, si assume che il membro sia astratto.
- se è presente un'implementazione e non sono specificati né abstract né sealed, si presume che il membro sia virtuale.
- Il modificatore sealed è necessario affinché un metodo non sia né virtuale né astratto.
In alternativa, è possibile dire che il modificatore virtuale è necessario per un membro virtuale. Ad esempio, se è presente un membro con implementazione non contrassegnata in modo esplicito con modificatore virtuale, non è né virtuale, né astratta. Questo approccio potrebbe offrire un'esperienza migliore quando un metodo viene spostato da una classe a un'interfaccia:
- un metodo astratto rimane astratto.
- un metodo virtuale rimane virtuale.
- un metodo senza alcun modificatore non rimane né virtuale né astratto.
- Il modificatore sealed non può essere applicato a un metodo che non è un override.
Che ne pensi?
Problema chiuso: Un metodo concreto (con implementazione) deve essere implicito
virtual
? [SÌ]
Decisioni: Prese nel LDM 2017-04-05:
- non virtuale deve essere espressa in modo esplicito tramite
sealed
oprivate
. -
sealed
è la parola chiave per rendere non virtuali i membri istanziati dell'interfaccia con corpo - Vogliamo consentire tutti i modificatori nelle interfacce
- L'accessibilità predefinita per i membri dell'interfaccia è pubblica, inclusi i tipi annidati
- i membri delle funzioni private nelle interfacce sono implicitamente sigillati e
sealed
non è consentito su di loro. - Le classi private (nelle interfacce) sono consentite e possono essere sigillate, e ciò significa "sigillate" nel senso della classe sigillata.
- In assenza di una buona proposta, il parziale non è ancora consentito sulle interfacce o sui relativi membri.
Compatibilità binaria 1
Quando una libreria fornisce un'implementazione predefinita
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}
Comprendiamo che l'implementazione di I1.M
in C
è I1.M
. Cosa accade se l'assembly contenente I2
viene modificato come segue e ricompilato
interface I2 : I1
{
override void M() { Impl2 }
}
ma C
non è ricompilato. Cosa accade quando viene eseguito il programma? Invocazione di (C as I1).M()
- Esecuzioni
I1.M
- Esecuzioni
I2.M
- Genera un qualche tipo di errore di runtime
Decisione: effettuata 2017-04-11: esegue I2.M
, che rappresenta l'override più specifico in fase di esecuzione senza ambiguità.
Funzioni di accesso agli eventi (chiuse)
Problema chiuso: è possibile eseguire l'override di un evento "a fasi"?
Si consideri questo caso:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
// error: "remove" accessor missing
}
}
Questa implementazione "parziale" dell'evento non è consentita perché, come in una classe, la sintassi per una dichiarazione di evento non consente solo un modificatore di accesso; devono essere forniti entrambi o nessuno. È possibile eseguire la stessa operazione consentendo all'accessor di rimozione astratto nella sintassi di essere implicitamente astratto dalla presenza dell'assenza di un corpo:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
remove; // implicitly abstract
}
}
Si noti che si tratta di una nuova sintassi (proposta). Nella grammatica corrente, le funzioni di accesso agli eventi hanno un corpo obbligatorio.
problema chiuso: una funzione di accesso a un evento può essere astratta in modo implicito dall'omissione di un corpo, in modo analogo al modo in cui i metodi nelle interfacce e nelle funzioni di accesso alle proprietà sono astratti (implicitamente) dall'omissione di un corpo?
Decisione: (2017-04-18) No, le dichiarazioni di eventi richiedono entrambe le funzioni di accesso concrete (o nessuno dei due).
Riabstrazione in una classe (chiusa)
Problema chiuso: Dovremmo confermare che sia consentito (in caso contrario, l'aggiunta di un'implementazione predefinita potrebbe essere una modifica importante):
interface I1
{
void M() { }
}
abstract class C : I1
{
public abstract void M(); // implement I1.M with an abstract method in C
}
Decisione: (2017-04-18) Sì, l'aggiunta di un corpo a una dichiarazione di membro dell'interfaccia non dovrebbe causare problemi a C.
Override sigillato (chiuso)
La domanda precedente presuppone implicitamente che il modificatore sealed
possa essere applicato a un override
in un'interfaccia. Ciò contraddice la bozza di specifica tecnica. Vogliamo consentire di sigillare un override? È necessario considerare gli effetti della sigillatura sulla compatibilità di origine e binaria.
problema chiuso: È consigliabile consentire la chiusura di un override?
Decisione: (2017-04-18) Non permettiamo sealed
sugli override nelle interfacce. L'unico uso di sealed
nei membri dell'interfaccia consiste nel renderli non virtuali nella dichiarazione iniziale.
Ereditarietà e classi a rombo (chiuse)
La bozza della proposta preferisce le sostituzioni di classe agli override dell'interfaccia negli scenari di ereditarietà dei diamanti:
È necessario che ogni interfaccia e classe disponga di un di override più specifico per ogni metodo di interfaccia tra gli override visualizzati nel tipo o nelle relative interfacce dirette e indirette. L'override più specifico è un univoco override più specifico di ogni altro override. Se non è presente alcun override, il metodo stesso viene considerato l'override più specifico.
Un
M1
di override viene considerato più specifico rispetto a un altroM2
di override seM1
viene dichiarato nel tipoT1
,M2
viene dichiarato nel tipoT2
e
T1
contieneT2
tra le interfacce dirette o indirette oppureT2
è un tipo di interfaccia, maT1
non è un tipo di interfaccia.
Lo scenario è questo
interface IA
{
void M();
}
interface IB : IA
{
override void M() { WriteLine("IB"); }
}
class Base : IA
{
void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
static void Main()
{
IA a = new Derived();
a.M(); // what does it do?
}
}
È consigliabile confermare questo comportamento (o decidere in altro modo)
Problema chiuso: Confermare la specifica bozza, sopra, per l'override più specifico , in quanto si applica alle classi e alle interfacce miste (una classe assume la priorità su un'interfaccia). Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes.
Metodi di interfaccia e struct (chiusi)
Esistono alcune interazioni sfortunate tra i metodi di interfaccia predefiniti e gli struct.
interface IA
{
public void M() { }
}
struct S : IA
{
}
Si noti che i membri dell'interfaccia non vengono ereditati:
var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'
Di conseguenza, il client deve effettuare il boxing dello struct per richiamare i metodi di interfaccia.
IA s = default(S); // an S, boxed
s.M(); // ok
In questo modo, il boxing vanifica i principali vantaggi di un tipo struct
. Inoltre, qualsiasi metodo di mutazione non avrà alcun effetto apparente, perché operano su una copia boxed dello struct:
interface IB
{
public void Increment() { P += 1; }
public int P { get; set; }
}
struct T : IB
{
public int P { get; set; } // auto-property
}
T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0
Problema chiuso: Cosa è possibile fare a riguardo:
- Impedire a un
struct
di ereditare un'implementazione predefinita. Tutti i metodi di interfaccia vengono considerati astratti in unstruct
. Quindi potremmo dedicare del tempo in un secondo momento per decidere come migliorarlo.- Elabora una strategia di generazione del codice che eviti l'uso del boxing. All'interno di un metodo come
IB.Increment
, il tipo dithis
potrebbe essere simile a un parametro di tipo vincolato aIB
. In concomitanza con ciò, per evitare di limitare il chiamante, i metodi non astratti verrebbero ereditati dalle interfacce. Ciò può aumentare notevolmente il lavoro del compilatore e dell'implementazione CLR.- Non preoccuparti e lascialo così com'è, una verruca.
- Altre idee?
Decisione: Non preoccuparti e lascialo come una verruca. Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations.
Chiamate all'interfaccia di base (chiuse)
Questa decisione non è stata implementata in C# 8. La sintassi base(Interface).M()
non è implementata.
La specifica bozza suggerisce una sintassi per le chiamate all'interfaccia di base basate su Java: Interface.base.M()
. È necessario selezionare una sintassi, almeno per il prototipo iniziale. Il mio preferito è base<Interface>.M()
.
Problema chiuso: Qual è la sintassi per una chiamata a un membro di base?
Decisione: La sintassi è base(Interface).M()
. Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation. L'interfaccia denominata deve essere un'interfaccia di base, ma non deve essere un'interfaccia di base diretta.
Problema aperto: Deve essere permesso l'uso delle chiamate all'interfaccia di base nei membri della classe?
Decisione: Sì. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation
Sovrascrittura dei membri dell'interfaccia non pubblica (riservato)
In un'interfaccia, i membri non pubblici delle interfacce di base vengono sottoposti a override usando il modificatore override
. Se si tratta di un override "esplicito" che denomina l'interfaccia contenente il membro, il modificatore di accesso viene omesso.
Problema chiuso: Se è un override "implicito" che non specifica l'interfaccia, il modificatore di accesso deve corrispondere?
Decisione: Solo i membri pubblici possono essere soggetti a override implicito e l'accesso deve corrispondere. Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.
Problema aperto: Il modificatore di accesso è obbligatorio, facoltativo o omesso in un override esplicito, ad esempio
override void IB.M() {}
?
Problema aperto: è
override
obbligatorio, facoltativo o omesso in un'esclusione esplicita comevoid IB.M() {}
?
In che modo si implementa un membro di interfaccia non pubblico in una classe? Forse deve essere fatto in modo esplicito?
interface IA
{
internal void MI();
protected void MP();
}
class C : IA
{
// are these implementations? Decision: NO
internal void MI() {}
protected void MP() {}
}
problema chiuso: Come si implementa un membro di interfaccia non pubblico in una classe?
Decisione: È possibile implementare solo membri dell'interfaccia non pubblici in modo esplicito. Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.
Decisione: nessuna parola chiave override
è permessa per i membri dell'interfaccia. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member
Compatibilità binaria 2 (chiusa)
Si consideri il codice seguente in cui ogni tipo si trova in un assembly separato
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}
Comprendiamo che l'implementazione di I1.M
in C
è I2.M
. Cosa accade se l'assembly contenente I3
viene modificato come segue e ricompilato
interface I3 : I1
{
override void M() { Impl3 }
}
ma C
non è ricompilato. Cosa accade quando viene eseguito il programma? Invocazione di (C as I1).M()
- Esecuzioni
I1.M
- Esecuzioni
I2.M
- Esecuzioni
I3.M
- 2 o 3, deterministicamente
- Genera un certo tipo di eccezione di runtime
Decisione: Genera un'eccezione (5). Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods.
Consentire partial
nell'interfaccia? (chiuso)
Dato che le interfacce possono essere usate in modo analogo al modo in cui vengono usate le classi astratte, può essere utile dichiararle partial
. Ciò sarebbe particolarmente utile quando si ha a che fare con i generatori.
proposta: Rimuovere la restrizione sulla lingua per cui le interfacce e i membri delle interfacce non possono essere dichiarati
partial
.
Decisione: Sì. Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface.
Main
in un'interfaccia? (chiuso)
Problema aperto: Il metodo
static Main
in un'interfaccia può essere un candidato come punto di ingresso del programma?
Decisione: Sì. Vedi https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface.
Confermare la finalità di supportare metodi pubblici non virtuali (chiusi)
È possibile confermare (o invertire) la decisione di consentire metodi pubblici non virtuali in un'interfaccia?
interface IA
{
public sealed void M() { }
}
Semi-Closed Problema: (2017-04-18) Pensiamo che sarà utile, ma torneremo ad esso. Questo è un ostacolo nel modello mentale.
Decisione: Sì. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#confirm-that-we-support-public-non-virtual-methods.
Un override
in un'interfaccia introduce un nuovo membro? (chiuso)
Esistono alcuni modi per osservare se una dichiarazione di override introduce o meno un nuovo membro.
interface IA
{
void M(int x) { }
}
interface IB : IA
{
override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
static void M2()
{
M(y: 3); // permitted? Decision: No.
}
override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}
Questione aperta: Una dichiarazione di override in un'interfaccia introduce un nuovo membro? (chiuso)
In una classe, un metodo di override è "visibile" in alcuni sensi. Ad esempio, i nomi dei suoi parametri hanno la precedenza sui nomi dei parametri nel metodo sovrascritto. Può essere possibile duplicare tale comportamento nelle interfacce, perché esiste sempre un override più specifico. Ma vogliamo duplicare questo comportamento?
Inoltre, è possibile "sovrascrivere" un metodo di override? [Discutibile]
Decisione: nessuna parola chiave override
è permessa per i membri dell'interfaccia.
https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member.
Proprietà con accesso privato (chiuso)
Si dice che i membri privati non sono virtuali e che la combinazione di virtuali e privati non è consentita. Ma che ne dici di una proprietà con un accessore privato?
interface IA
{
public virtual int P
{
get => 3;
private set { }
}
}
Questo è consentito? L'accessore set
è qui virtual
o no? Può essere sovrascritto dove è accessibile? Di seguito viene implementato in modo implicito solo l'accessor get
?
class C : IA
{
public int P
{
get => 4;
set { }
}
}
Si tratta presumibilmente di un errore perché IA.P.set non è virtuale e non è accessibile.
class C : IA
{
int IA.P
{
get => 4;
set { } // Decision: Not valid
}
}
Decisione: il primo esempio sembra valido, mentre l'ultimo non lo è. Questo problema viene risolto in modo analogo a come funziona già in C#. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor
Chiamate all'interfaccia di base, turno 2 (chiuso)
Questa operazione non è stata implementata in C# 8.
La nostra precedente soluzione per gestire le invocazioni di base non fornisce effettivamente un'espressività sufficiente. Si scopre che in C# e CLR, a differenza di Java, è necessario specificare sia l'interfaccia contenente la dichiarazione del metodo che la posizione dell'implementazione che si vuole richiamare.
Propongo la sintassi seguente per le chiamate di base nelle interfacce. Non sono innamorato di esso, ma illustra ciò che qualsiasi sintassi deve essere in grado di esprimere:
interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
void I1.M()
{
base<I3>(I1).M(); // calls I3's implementation of I1.M
base<I4>(I1).M(); // calls I4's implementation of I1.M
}
void I2.M()
{
base<I3>(I2).M(); // calls I3's implementation of I2.M
base<I4>(I2).M(); // calls I4's implementation of I2.M
}
}
Se non c'è ambiguità, è possibile scriverla più semplicemente
interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
void I1.M()
{
base<I3>.M(); // calls I3's implementation of I1.M
base<I4>.M(); // calls I4's implementation of I1.M
}
}
O
interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
void I1.M()
{
base(I1).M(); // calls I3's implementation of I1.M
}
void I2.M()
{
base(I2).M(); // calls I3's implementation of I2.M
}
}
O
interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
void I1.M()
{
base.M(); // calls I3's implementation of I1.M
}
}
decisione: deciso su base(N.I1<T>).M(s)
, concedendo che se si dispone di un'associazione di chiamata potrebbe esserci un problema più avanti. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations
Avviso per una struttura che non implementa il metodo predefinito? (chiuso)
@vancem afferma che è consigliabile considerare seriamente la generazione di un avviso se una dichiarazione di tipo valore non riesce a eseguire l'override di un metodo di interfaccia, anche se erediterebbe un'implementazione di tale metodo da un'interfaccia. Perché provoca il boxing e mina le chiamate vincolate.
Decisione: questo sembra più adatto per un analizzatore. Sembra anche che questo avviso potrebbe essere rumoroso, perché verrebbe generato anche se il metodo di interfaccia predefinito non viene mai chiamato e non si verificherà mai alcun boxing. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method
Costruttori statici dell'interfaccia (chiusi)
Quando vengono eseguiti i costruttori statici dell'interfaccia? La bozza corrente della CLI propone che avvenga quando si accede al primo metodo statico o campo. Se non ce n'è nessuno di quelli allora potrebbe non essere mai eseguito?
[2018-10-09 Il team CLR propone "Andando a eseguire il mirroring di ciò che facciamo per i valuetype (controllo dell'accesso a ogni metodo di istanza)"]
Decision: i costruttori statici vengono eseguiti anche all'ingresso ai metodi di istanza, se il costruttore statico non è beforefieldinit
, nel qual caso i costruttori statici vengono eseguiti prima dell'accesso al primo campo statico. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run
Riunioni di progettazione
Note riunione LDM 8 marzo 201721 marzo 2017 Note riunione LDM23 marzo 2017 riunione "Comportamento CLR per metodi di interfaccia predefiniti"5 aprile 2017 Note riunione LDM11 aprile 2017 Note riunione LDM18 aprile 2017 Note riunione LDM19 aprile 2017 Note riunione LDM17 maggio 2017 Note riunione LDM31 maggio 2017 Note riunione LDM14 giugno 2017 Note riunione LDM17 ottobre 2018 Note riunione LDM14 novembre 2018 Note riunione LDM
C# feature specifications