Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Annotazioni
Questo articolo è una specifica delle 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 riportate nelle note pertinenti della riunione di progettazione linguistica (LDM) .
Ulteriori dettagli sul processo di adozione delle specifiche di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche .
Questione prioritaria: https://github.com/dotnet/csharplang/issues/4436
Riassunto
Un'interfaccia può specificare membri statici astratti che implementano classi e struct sono quindi necessari per fornire un'implementazione esplicita o implicita di . È possibile accedere ai membri di parametri di tipo vincolati dall'interfaccia .
Motivazione
Attualmente non è possibile astrarre su membri statici e scrivere codice generalizzato che si applica a tutti i tipi che definiscono tali membri statici. Ciò è particolarmente problematico per i tipi di membri che esistono solo in una forma statica, in particolare gli operatori.
Questa funzionalità consente algoritmi generici su tipi numerici, rappresentati da vincoli di interfaccia che specificano la presenza di operatori specificati. Gli algoritmi possono quindi essere espressi in termini di tali operatori:
// Interface specifies static properties and operators
interface IAddable<T> where T : IAddable<T>
{
static abstract T Zero { get; }
static abstract T operator +(T t1, T t2);
}
// Classes and structs (including built-ins) can implement interface
struct Int32 : …, IAddable<Int32>
{
static Int32 IAddable.operator +(Int32 x, Int32 y) => x + y; // Explicit
public static int Zero => 0; // Implicit
}
// Generic algorithms can use static members on T
public static T AddAll<T>(T[] ts) where T : IAddable<T>
{
T result = T.Zero; // Call static operator
foreach (T t in ts) { result += t; } // Use `+`
return result;
}
// Generic method can be applied to built-in and user-defined types
int sixtyThree = AddAll(new [] { 1, 2, 4, 8, 16, 32 });
Sintassi
Membri di interfaccia
La funzionalità consente di dichiarare virtuali i membri dell'interfaccia statica.
Regole prima di C# 11
Prima di C# 11, i membri dell'istanza nelle interfacce sono implicitamente astratti (o virtuali se hanno un'implementazione predefinita), ma facoltativamente possono avere un abstract modificatore (o virtual). I membri dell'istanza non virtuale devono essere contrassegnati in modo esplicito come sealed.
I membri dell'interfaccia statica oggi sono implicitamente non virtuali e non consentono abstractvirtual modificatori o sealed .
Proposta
Membri statici astratti
I membri dell'interfaccia statica diversi dai campi possono avere anche il abstract modificatore. I membri statici astratti non possono avere un corpo (o nel caso delle proprietà, le funzioni di accesso non possono avere un corpo).
interface I<T> where T : I<T>
{
static abstract void M();
static abstract T P { get; set; }
static abstract event Action E;
static abstract T operator +(T l, T r);
static abstract bool operator ==(T l, T r);
static abstract bool operator !=(T l, T r);
static abstract implicit operator T(string s);
static abstract explicit operator string(T t);
}
Membri statici virtuali
I membri dell'interfaccia statica diversi dai campi possono avere anche il virtual modificatore. I membri statici virtuali devono avere un corpo.
interface I<T> where T : I<T>
{
static virtual void M() {}
static virtual T P { get; set; }
static virtual event Action E;
static virtual T operator +(T l, T r) { throw new NotImplementedException(); }
}
Membri statici non virtuali in modo esplicito
Per la simmetria con membri dell'istanza non virtuale, i membri statici (ad eccezione dei campi) devono essere consentiti un modificatore facoltativo sealed , anche se non sono virtuali per impostazione predefinita:
interface I0
{
static sealed void M() => Console.WriteLine("Default behavior");
static sealed int f = 0;
static sealed int P1 { get; set; }
static sealed int P2 { get => f; set => f = value; }
static sealed event Action E1;
static sealed event Action E2 { add => E1 += value; remove => E1 -= value; }
static sealed I0 operator +(I0 l, I0 r) => l;
}
Implementazione dei membri dell'interfaccia
Regole di oggi
Le classi e gli struct possono implementare membri di istanze astratte di interfacce in modo implicito o esplicito. Un membro dell'interfaccia implementato in modo implicito è una normale dichiarazione membro (virtuale o non virtuale) della classe o dello struct che semplicemente "accade" per implementare anche il membro dell'interfaccia. Il membro può anche essere ereditato da una classe di base e quindi non essere nemmeno presente nella dichiarazione di classe.
Un membro dell'interfaccia implementato in modo esplicito usa un nome completo per identificare il membro dell'interfaccia in questione. L'implementazione non è direttamente accessibile come membro della classe o dello struct, ma solo tramite l'interfaccia .
Proposta
Non sono necessarie nuove sintassi nelle classi e negli struct per facilitare l'implementazione implicita di membri dell'interfaccia astratta statica. Le dichiarazioni di membri statici esistenti servono a tale scopo.
Le implementazioni esplicite dei membri dell'interfaccia astratta statica usano un nome completo insieme al static modificatore.
class C : I<C>
{
string _s;
public C(string s) => _s = s;
static void I<C>.M() => Console.WriteLine("Implementation");
static C I<C>.P { get; set; }
static event Action I<C>.E // event declaration must use field accessor syntax
{
add { ... }
remove { ... }
}
static C I<C>.operator +(C l, C r) => new C($"{l._s} {r._s}");
static bool I<C>.operator ==(C l, C r) => l._s == r._s;
static bool I<C>.operator !=(C l, C r) => l._s != r._s;
static implicit I<C>.operator C(string s) => new C(s);
static explicit I<C>.operator string(C c) => c._s;
}
Semantica
Restrizioni dell'operatore
Attualmente tutte le dichiarazioni di operatori unari e binari hanno alcuni requisiti che coinvolgono almeno uno dei relativi operandi di tipo T o T?, dove T è il tipo di istanza del tipo di inclusione.
Questi requisiti devono essere limitati in modo che un operando con restrizioni possa essere di un parametro di tipo che conta come "il tipo di istanza del tipo di inclusione".
Affinché un parametro T di tipo venga conteggiato come "il tipo di istanza del tipo di inclusione", deve soddisfare i requisiti seguenti:
-
Tè un parametro di tipo diretto nell'interfaccia in cui si verifica la dichiarazione dell'operatore e -
Tè direttamente vincolato da ciò che la specifica chiama il "tipo di istanza", ovvero l'interfaccia circostante con i propri parametri di tipo usati come argomenti di tipo.
Operatori e conversioni di uguaglianza
Dichiarazioni astratte/virtuali di == operatori e != , nonché dichiarazioni astratte/virtuali di operatori di conversione impliciti ed espliciti saranno consentite nelle interfacce. Anche le interfacce derivate potranno implementarle.
Per == gli operatori e != , almeno un tipo di parametro deve essere un parametro di tipo che conta come "il tipo di istanza del tipo di inclusione", come definito nella sezione precedente.
Implementazione di membri astratti statici
Le regole per quando una dichiarazione di membro statico in una classe o uno struct viene considerata l'implementazione di un membro di interfaccia astratta statica e per quali requisiti si applicano quando lo fa, sono uguali a per i membri dell'istanza.
TBD: in questo caso potrebbero essere necessarie regole aggiuntive o diverse che non abbiamo ancora pensato.
Interfacce come argomenti di tipo
È stato illustrato il problema generato da https://github.com/dotnet/csharplang/issues/5955 e si è deciso di aggiungere una restrizione sull'utilizzo di un'interfaccia come argomento di tipo (https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts). Ecco la restrizione proposta da https://github.com/dotnet/csharplang/issues/5955 e approvata dal LDM.
Un'interfaccia contenente o eredita un membro astratto/virtuale statico che non dispone dell'implementazione più specifica nell'interfaccia non può essere utilizzata come argomento di tipo. Se tutti i membri astratti/virtuali statici hanno un'implementazione più specifica, l'interfaccia può essere usata come argomento di tipo.
Accesso ai membri dell'interfaccia astratta statica
È possibile accedere a un membro M di interfaccia astratta statica su un parametro T di tipo usando l'espressione T.M quando T è vincolato da un'interfaccia I ed M è un membro astratto statico accessibile di I.
T M<T>() where T : I<T>
{
T.M();
T t = T.P;
T.E += () => { };
return t + T.P;
}
In fase di esecuzione, l'implementazione effettiva del membro usata è quella presente nel tipo effettivo fornito come argomento di tipo.
C c = M<C>(); // The static members of C get called
Poiché le espressioni di query sono specifiche come riscrittura sintattica, C# in realtà consente di usare un tipo come origine query, purché disponga di membri statici per gli operatori di query usati. In altre parole, se la sintassi si adatta, lo consentiamo! Questo comportamento non è intenzionale o importante nell'originale LINQ e non si vuole eseguire il lavoro per supportarlo sui parametri di tipo. Se ci sono scenari là fuori, si sentirà parlare di loro e si può scegliere di adottare questo in un secondo momento.
Sicurezza della varianza §18.2.3.2
Le regole di sicurezza della varianza devono essere applicate alle firme di membri astratti statici. L'aggiunta proposta in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/variance-safety-for-static-interface-members.md#variance-safety deve essere modificata da
Queste restrizioni non si applicano alle occorrenze di tipi all'interno di dichiarazioni di membri statici.
a
Queste restrizioni non si applicano alle occorrenze di tipi all'interno di dichiarazioni di membri statici non virtuali e non astratti .
§10.5.4 Conversioni implicite definite dall'utente
Punti elenco seguenti
- Determinare i tipi
SeS₀T₀.- Se
Eha un tipo, lasciareSche sia quel tipo. - Se
SoTsono tipi valore nullable, lasciareSᵢeTᵢessere i relativi tipi sottostanti, in caso contrario lasciareSᵢeTᵢessereSrispettivamente eT. - Se
SᵢoTᵢsono parametri di tipo, lasciareS₀eT₀essere le relative classi di base valide, in caso contrario lasciareS₀eT₀essereSₓrispettivamente eTᵢ.
- Se
- Trovare il set di tipi,
D, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito daS0(seS0è una classe o uno struct), le classi di base diS0(seS0è una classe) eT0(seT0è una classe o uno struct). - Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U. Questo set è costituito dagli operatori di conversione impliciti definiti dall'utente e lifted dichiarati dalle classi o dagli struct inDche converte da un tipo che includeSa un tipo incluso daT. SeUè vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.
sono regolati come segue:
- Determinare i tipi
SeS₀T₀.- Se
Eha un tipo, lasciareSche sia quel tipo. - Se
SoTsono tipi valore nullable, lasciareSᵢeTᵢessere i relativi tipi sottostanti, in caso contrario lasciareSᵢeTᵢessereSrispettivamente eT. - Se
SᵢoTᵢsono parametri di tipo, lasciareS₀eT₀essere le relative classi di base valide, in caso contrario lasciareS₀eT₀essereSₓrispettivamente eTᵢ.
- Se
- Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U.- Trovare il set di tipi,
D1, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito daS0(seS0è una classe o uno struct), le classi di base diS0(seS0è una classe) eT0(seT0è una classe o uno struct). - Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U1. Questo set è costituito dagli operatori di conversione impliciti definiti dall'utente e lifted dichiarati dalle classi o dagli struct inD1che converte da un tipo che includeSa un tipo incluso daT. - Se
U1non è vuoto,UèU1. In caso contrario, .- Trovare il set di tipi,
D2, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito da un setSᵢinterfacce efficace e le relative interfacce di base (seSᵢè un parametro di tipo) eTᵢun set di interfacce effettivo (seTᵢè un parametro di tipo). - Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U2. Questo set è costituito dagli operatori di conversione impliciti definiti dall'utente e lifted dichiarati dalle interfacce inD2che converte da un tipo che includeSa un tipo incluso daT. - Se
U2non è vuoto,Uallora èU2
- Trovare il set di tipi,
- Trovare il set di tipi,
- Se
Uè vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.
Conversioni esplicite definite dall'utente di §10.3.9
Punti elenco seguenti
- Determinare i tipi
SeS₀T₀.- Se
Eha un tipo, lasciareSche sia quel tipo. - Se
SoTsono tipi valore nullable, lasciareSᵢeTᵢessere i relativi tipi sottostanti, in caso contrario lasciareSᵢeTᵢessereSrispettivamente eT. - Se
SᵢoTᵢsono parametri di tipo, lasciareS₀eT₀essere le relative classi di base valide, in caso contrario lasciareS₀eT₀essereSᵢrispettivamente eTᵢ.
- Se
- Trovare il set di tipi,
D, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito da (seS0è una classe o uno struct), le classi di base diS0(se è una classe),S0(seS0T0è una classe o uno struct) e le classi base diT0(seT0è unaT0classe). - Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle classi o dagli struct inDche convertono da un tipo che include o è incluso inSa un tipo che include o è incluso inT. SeUè vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.
sono regolati come segue:
- Determinare i tipi
SeS₀T₀.- Se
Eha un tipo, lasciareSche sia quel tipo. - Se
SoTsono tipi valore nullable, lasciareSᵢeTᵢessere i relativi tipi sottostanti, in caso contrario lasciareSᵢeTᵢessereSrispettivamente eT. - Se
SᵢoTᵢsono parametri di tipo, lasciareS₀eT₀essere le relative classi di base valide, in caso contrario lasciareS₀eT₀essereSᵢrispettivamente eTᵢ.
- Se
- Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U.- Trovare il set di tipi,
D1, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito da (seS0è una classe o uno struct), le classi di base diS0(se è una classe),S0(seS0T0è una classe o uno struct) e le classi base diT0(seT0è unaT0classe). - Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U1. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle classi o dagli struct inD1che convertono da un tipo che include o è incluso inSa un tipo che include o è incluso inT. - Se
U1non è vuoto,UèU1. In caso contrario, .- Trovare il set di tipi,
D2, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito da un setSᵢinterfacce efficace e le relative interfacce di base (seSᵢè un parametro di tipo) eTᵢun set di interfacce di interfaccia efficace e le relative interfacce di base (seTᵢè un parametro di tipo). - Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili,
U2. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle interfacce inD2che vengono convertite da un tipo che include o include daSa un tipo che include o include daT. - Se
U2non è vuoto,Uallora èU2
- Trovare il set di tipi,
- Trovare il set di tipi,
- Se
Uè vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.
Implementazioni predefinite
Una funzionalità aggiuntiva di questa proposta consiste nel consentire ai membri virtuali statici nelle interfacce di avere implementazioni predefinite, proprio come fanno i membri virtuali/astratti dell'istanza.
Una complicazione è che le implementazioni predefinite vogliono chiamare altri membri virtuali statici "virtualmente". Consentire la chiamata diretta dei membri virtuali statici nell'interfaccia richiederebbe il flusso di un parametro di tipo nascosto che rappresenta il tipo "self" su cui è stato effettivamente richiamato il metodo statico corrente. Questo sembra complicato, costoso e potenzialmente confuso.
È stata illustrata una versione più semplice che mantiene le limitazioni della proposta corrente che i membri virtuali statici possono essere richiamati solo sui parametri di tipo. Poiché le interfacce con membri virtuali statici hanno spesso un parametro di tipo esplicito che rappresenta un tipo "self", non si tratta di una perdita notevole: altri membri virtuali statici potrebbero essere chiamati solo su tale tipo. Questa versione è molto più semplice e sembra abbastanza fattibile.
Abbiamo https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics deciso di supportare le implementazioni predefinite dei membri statici che seguono/espandendo le regole stabilite di https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md conseguenza.
Corrispondenza dei criteri
Dato il codice seguente, un utente potrebbe ragionevolmente aspettarsi di stampare "True" (come se il modello costante fosse scritto inline):
M(1.0);
static void M<T>(T t) where T : INumberBase<T>
{
Console.WriteLine(t is 1); // Error. Cannot use a numeric constant
Console.WriteLine((t is int i) && (i is 1));
}
Tuttavia, poiché il tipo di input del criterio non doubleè , il criterio costante 1 verificherà innanzitutto il valore in ingresso T rispetto inta . Ciò non è intuitivo, quindi viene bloccato fino a quando una versione futura di C# non aggiunge una migliore gestione per la corrispondenza numerica rispetto ai tipi derivati da INumberBase<T>. A tale scopo, si dirà che, si riconoscerà INumberBase<T> in modo esplicito come tipo da cui derivano tutti i "numeri" e si blocca il modello se si sta tentando di trovare una corrispondenza con un modello costante numerico rispetto a un tipo numerico che non è possibile rappresentare il modello in (ad esempio, un parametro di tipo vincolato a INumberBase<T>o un tipo di numero definito dall'utente che eredita da INumberBase<T>).
Si aggiunge formalmente un'eccezione alla definizione di modelli compatibili con i criteri costanti:
Un modello costante confronta il valore di un'espressione con un valore costante. La costante può essere qualsiasi espressione costante, ad esempio un valore letterale, il nome di una variabile
constdichiarata o una costante di enumerazione. Quando il valore di input non è un tipo aperto, l'espressione costante viene convertita in modo implicito nel tipo dell'espressione corrispondente; se il tipo del valore di input non è compatibile con i criteri con il tipo dell'espressione costante, l'operazione di corrispondenza dei criteri è un errore. Se l'espressione costante confrontata con è un valore numerico, il valore di input è un tipo che eredita daSystem.Numerics.INumberBase<T>e non esiste alcuna conversione costante dall'espressione costante al tipo del valore di input, l'operazione di corrispondenza dei criteri è un errore.
Si aggiunge anche un'eccezione simile per i modelli relazionali:
Quando l'input è un tipo per il quale è definito un operatore relazionale binario predefinito appropriato, applicabile con l'input come operando sinistro e la costante fornita come operando destro, la valutazione di tale operatore viene considerata come il significato del modello relazionale. In caso contrario, l'input viene convertito nel tipo dell'espressione usando una conversione esplicita nullable o unboxing. Si tratta di un errore in fase di compilazione se non esiste alcuna conversione di questo tipo. Si tratta di un errore in fase di compilazione se il tipo di input è un parametro di tipo vincolato a o un tipo che eredita da
System.Numerics.INumberBase<T>e il tipo di input non ha un operatore relazionale binario predefinito appropriato definito. Il modello viene considerato non corrispondente se la conversione non riesce. Se la conversione ha esito positivo, il risultato dell'operazione di corrispondenza dei criteri è il risultato della valutazione dell'espressione e OP v dove e è l'input convertito, OP è l'operatore relazionale e v è l'espressione costante.
Svantaggi
- "abstract statico" è un nuovo concetto e aggiungerà significativamente al carico concettuale di C#.
- Non è una funzionalità economica da compilare. Dovremmo assicurarci che ne valga la pena.
Le alternative
Vincoli strutturali
Un approccio alternativo consiste nell'avere direttamente "vincoli strutturali" e richiedere in modo esplicito la presenza di operatori specifici in un parametro di tipo. Gli svantaggi di questo sono: - Questo dovrebbe essere scritto ogni volta. La presenza di un vincolo denominato sembra migliore. - Si tratta di un nuovo tipo di vincolo, mentre la funzionalità proposta utilizza il concetto esistente di vincoli di interfaccia. - Funziona solo per gli operatori, non (facilmente) altri tipi di membri statici.
Domande non risolte
Interfacce astratte statiche e classi statiche
Per altre informazioni, vedere https://github.com/dotnet/csharplang/issues/5783 e https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes.
Incontri di design
- https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-08.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-05.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-06.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md
C# feature specifications