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

È stato utile?

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top