operatori impliciti equivalenti: perché sono legali?
-
01-10-2019 - |
Domanda
Update!
Vedere la mia dissezione di una parte del C # spec di seguito; Penso che devo essere perso qualcosa, perché per me sembra che il comportamento che sto descrivendo in questa domanda in realtà viola la spec.
Aggiornamento 2!
OK, dopo un'ulteriore riflessione, e sulla base di alcuni commenti, penso che ora capisco cosa sta succedendo. Le parole "tipo di origine" nelle specifiche si riferiscono al tipo in fase di conversione dal - vale a dire, Type2
nel mio esempio qui sotto - che significa semplicemente che il compilatore è in grado di restringere i candidati verso il basso per i due operatori definito (poiché Type2
è il tipo di fonte per entrambi). Tuttavia, non è possibile restringere le scelte ulteriori. Così le parole chiave nel spec (in quanto si applica a questa domanda) sono "tipo di origine" , che ho già mal interpretato (credo) il significato di "tipo dichiarando."
Original domanda
Dire che ho questi tipi definiti:
class Type0
{
public string Value { get; private set; }
public Type0(string value)
{
Value = value;
}
}
class Type1 : Type0
{
public Type1(string value) : base(value) { }
public static implicit operator Type1(Type2 other)
{
return new Type1("Converted using Type1's operator.");
}
}
class Type2 : Type0
{
public Type2(string value) : base(value) { }
public static implicit operator Type1(Type2 other)
{
return new Type1("Converted using Type2's operator.");
}
}
Poi dico faccio questo:
Type2 t2 = new Type2("B");
Type1 t1 = t2;
Ovviamente questo è ambigua, in quanto non è chiaro quale operatore implicit
da utilizzare. La mia domanda è - dal momento che non riesco a vedere qualsiasi modo per risolvere questa ambiguità (non è come posso eseguire alcune cast esplicito per chiarire quale versione che voglio), e tuttavia le definizioni delle classi di cui sopra di compilazione -? perché il compilatore consentire a tali operatori di corrispondenza implicit
a tutti
Dissection
OK, ho intenzione di passo attraverso l'estratto del C # spec citato da Hans Passant, nel tentativo di dare un senso a questo.
Trova l'insieme dei tipi, D, da cui operatori di conversione definiti dall'utente sarà essere considerato. Questo insieme è costituito da S (Se S è una classe o struct), la base classi di S (se S è una classe), e T (Se T è una classe o struct).
Stiamo convertendo da Type2
( S ) a Type1
( T ). Così sembra che qui D dovrebbe includere tutti e tre i tipi nell'esempio: Type0
(perché è una classe base di S ), Type1
( T ) e Type2
( S ).
Trova l'insieme di applicabile operatori di conversione definiti dall'utente, U. Questo insieme costituito dal definita dall'utente operatori di conversione implicito dichiarati dalle classi o strutture a D che convertire da un tipo che comprende S un tipo abbracciato da T. Se U è svuotare, la conversione è definita e si verifica un errore di compilazione.
Va bene, abbiamo due operatori che soddisfano queste condizioni. La versione dichiarato Type1
soddisfa i requisiti perché Type1
è in D e converte da Type2
(che ovviamente comprende S ) per Type1
(che è ovviamente comprendeva da T ). La versione in Type2
anche soddisfa i requisiti per esattamente le stesse ragioni. Così U include entrambi questi operatori.
Infine, per quanto riguarda la ricerca più specifica "tipo di origine" SX degli operatori U :
Se uno qualsiasi degli operatori U convertito da S, allora SX è S.
Ora, sia operatori di U convertire da S - quindi questo mi dice che SX è < strong> S .
non questo significa che la versione Type2
dovrebbe essere utilizzato?
Ma aspettate! Sono confuso!
ho fatto a non solo la versione di Type1
definito dell'operatore, nel qual caso, il candidato unico rimasto sarebbe la versione di Type1
, eppure secondo la spec SX sarebbe Type2
? Questo mi sembra un possibile scenario in cui la cosa spec mandati impossibile (vale a dire, che la conversione dichiarato nel Type2
dovrebbe essere usato quando in realtà non esiste).
Soluzione
Non vogliamo davvero che sia un errore di compilazione solo per definire le conversioni che potrebbe causa ambiguità. Supponiamo di alterare Type0 per memorizzare un doppio, e per qualche motivo vogliamo fornire conversioni separati per intero con segno e intero senza segno.
class Type0
{
public double Value { get; private set; }
public Type0(double value)
{
Value = value;
}
public static implicit operator Int32(Type0 other)
{
return (Int32)other.Value;
}
public static implicit operator UInt32(Type0 other)
{
return (UInt32)Math.Abs(other.Value);
}
}
Questa compila bene, e posso usare l'utilizzo sia con conversioni
Type0 t = new Type0(0.9);
int i = t;
UInt32 u = t;
Tuttavia, è un errore di compilazione di provare float f = t
perché o delle conversioni implicite potrebbero essere usati per raggiungere un tipo intero che può poi essere convertito in float.
vogliamo solo il compilatore di cui lamentarsi queste ambiguità più complesse quando sono effettivamente utilizzati, dal momento che vorremmo la Type0 sopra per compilazione. Per coerenza, l'ambiguità più semplice dovrebbe causare un errore nel punto lo si utilizza piuttosto che quando si definisce.
Modifica
Da Hans rimosso la sua risposta che cita la specifica, ecco una breve corsa attraverso la parte del C # spec che determina se una conversione è ambiguo, avendo definito U come l'insieme di tutte le conversioni che potrebbe fare il lavoro:
- Trova il più tipo di fonte specifica, SX, degli operatori U:
- Se uno qualsiasi degli operatori U convertito da S, allora SX è S.
- Altrimenti, SX è il tipo più inglobato nel sistema combinato di tipi di destinazione degli operatori U. Se nessun tipo più racchiuso può essere trovato, quindi la conversione è ambigua e si verifica un errore di compilazione.
Parafrasato, preferiamo una conversione che converte direttamente da S, altrimenti preferiamo il tipo che è più "facile" per convertire S a. In entrambi gli esempi, abbiamo due conversioni da S disponibili. Se non ci fossero le conversioni da Type2
, preferiremmo una conversione da Type0
oltre un da object
. Se nessuno tipo è ovviamente la scelta migliore per convertire da, non riusciamo qui.
- Trova il più tipo di destinazione specifica, TX, degli operatori U:
- Se uno qualsiasi degli operatori U convertito T, allora TX è T.
- Altrimenti, TX è il tipo più comprende nell'insieme combinato di tipi di destinazione degli operatori U. Se nessun tipo più comprendenti può essere trovato, quindi la conversione è ambigua e si verifica un errore di compilazione.
Ancora una volta, noi preferiremmo di convertire direttamente a T, ma ci accontentiamo per il tipo che è più "facile" per convertire l'esempio di T. In Dan, abbiamo due conversioni al T disponibile. Nel mio esempio, i possibili obiettivi sono Int32
e UInt32
, e nessuno dei due è una partita migliore rispetto agli altri, quindi questo è dove la conversione non riesce. Il compilatore non ha modo di sapere se i mezzi float f = t
float f = (float)(Int32)t
o float f = (float)(UInt32)t
.
- Se U contiene esattamente un operatore di conversione definita dall'utente che converte da SX a TX, allora questo è il più operatore di conversione specifica. Se non esiste alcun operatore, o se è presente più di un tale operatore, quindi la conversione è ambigua e si verifica un errore di compilazione.
L'esempio di In Dan, non riusciamo qui perché abbiamo due conversioni lasciati da SX a TX. Potremmo non avere conversioni da SX a TX se abbiamo scelto diverse conversioni al momento di decidere SX e TX. Ad esempio, se avessimo un Type1a
derivato da Type1
, allora potremmo avere conversioni da Type2
a Type1a
e da Type0
a Type1
Questi sarebbe ancora darci SX = Type2 e TX = Type1, ma in realtà non hanno alcuna conversione da Tipo2 a Type1. Questo va bene, perché questo è davvero ambiguo. Il compilatore non sa se convertire Type2 a Type1a e poi gettato a Type1, o cast Type0 prima in modo che possa utilizzare che la conversione a Type1.
Altri suggerimenti
In ultima analisi, non può essere prohibitted con pieno successo. Tu ed io potremmo pubblicare due assemblee. Essi si potrebbe iniziare ad usare l'un l'altro assembla, durante l'aggiornamento la nostra. Allora potremmo ogni fornire cast impliciti tra tipi definiti in ogni gruppo. Solo quando abbiamo comunicato la prossima versione, potrebbe questo essere catturati, piuttosto che al momento della compilazione.
C'è un vantaggio nel non cercando di vietare le cose che non possono essere vietati, come si fa per chiarezza e coerenza (e c'è una lezione per i legislatori in questo).