Domanda riguardante le conversioni implicite nelle specifiche del linguaggio C #

La sezione 6.1 Conversioni implicite definisce una conversione di id quadro in questo modo:

Una conversione di identity framework converte da qualsiasi tipo allo stesso tipo. Questa conversione esiste in modo tale che un’ quadro che ha già un tipo richiesto può essere detta convertibile in quel tipo.

Ora, qual è lo scopo di frasi come queste?

(In §6.1.6 Conversioni implicite di riferimento)

Le conversioni implicite di riferimento sono:

  • […]
  • Da qualsiasi tipo di riferimento a un tipo di riferimento T se ha un’id quadro implicita o una conversione di riferimento a un tipo di riferimento T 0 e T 0 ha una conversione dell’identity framework in T

e:

(In § 6.1.7 conversioni di boxe)

  • Un tipo di valore ha una conversione di boxe in un’interfaccia di tipo I se ha una conversione di boxing in un’interfaccia di tipo I 0 e I 0 ha una conversione di identity framework in I

Inizialmente sembrano ridondanti (tautologhi). Ma devono essere lì per uno scopo, quindi perché sono lì?

Puoi dare un esempio di due tipi T 1 , T 2 in modo tale che T 1 non sia implicitamente convertibile in T 2 se non fosse per i paragrafi sopra citati?

La sezione 4.7 della specifica nota che esiste una conversione di id quadro da Foo a Foo e viceversa. La parte delle specifiche che hai citato è scritta per garantire che questo caso sia gestito. Cioè, se c’è una conversione di riferimento implicita da T a C allora c’è anche una conversione di riferimento implicita a C , C e C .

Si potrebbe ragionevolmente sottolineare che (1) l’intenzione di queste frasi non è ovvia – da qui la tua domanda – e confusa, e (2) che la sezione sulle conversioni di id quadro dovrebbe fare riferimento incrociato alla sezione sulle conversioni dinamiche, e (3) frasi in questo modo nelle specifiche è difficile per un implementatore delle specifiche tradurre chiaramente il linguaggio delle specifiche in un’implementazione. Come si può sapere se esiste un tale tipo? Le specifiche non devono specificare algoritmi esatti, ma sarebbe bello se fornisse più indicazioni.

Lo spec è, purtroppo, non un documento perfetto.

Aggiornamento il 22 settembre 2010:

Dubito che qualcuno leggerà questo oltre a Timwi. Anche così, ho voluto apportare alcune modifiche a questa risposta alla luce del fatto che ora è stata accettata una nuova risposta e il dibattito continua (almeno nel mio mondo forse immaginario) sull’opportunità o meno degli estratti citati delle specifiche sono tecnicamente ridondanti. Non aggiungo molto, ma è troppo consistente per rientrare in un commento. La maggior parte dell’aggiornamento può essere trovata sotto l’intestazione “Conversione che coinvolge il tipo dynamic ” di seguito.


Aggiornamento del 19 settembre 2010:

Nel tuo commento:

[T] il suo non ha senso.

Accidenti, Timwi, lo dici molto . Ma va bene, allora; mi hai messo sulla difensiva, quindi ecco qui!

Disclaimer: non ho assolutamente esaminato le specifiche il più strettamente ansible. Sulla base di alcune delle tue ultime domande sembra che tu ci stia guardando un po ‘ultimamente. Questo ti renderà più familiare con molti dettagli rispetto alla maggior parte degli utenti su SO; quindi questo, come la maggior parte delle risposte che potresti ricevere da chiunque tranne Eric Lippert, potrebbe non soddisfarti.

Premesse diverse

In primo luogo, la premessa della tua domanda è che se le affermazioni evidenziate sono ridondanti , allora non servono a nulla. La mia premessa è che le affermazioni ridondanti non sono necessariamente prive di scopo se chiariscono qualcosa che non è ovvio per tutti . Queste sono premesse contraddittorie. E se non siamo d’accordo sui presupposti, non possiamo avere un argomento logico diretto. Ti stavo solo chiedendo di ripensare alla tua premessa.

La tua risposta, tuttavia, è stata quella di ribadire la tua premessa: “Se le frasi sono veramente ridondanti, allora confondono solo il lettore e non chiariscono nulla”.

(Mi piace come ti sei impostato come rappresentante per tutti i lettori delle specifiche lì, a proposito.)

Non posso biasimarti per aver mantenuto questa posizione, esattamente. Voglio dire, sembra ovvio. E non ho fornito esempi concreti nella mia risposta originale. Quindi di seguito cercherò di includere alcuni esempi concreti. Ma prima, lasciatemi fare un passo indietro e offrire la mia opinione sul perché questo strano concetto di conversione dell’identity framework esiste nelle specifiche in primo luogo.

Lo scopo della definizione di conversione dell’id quadro

A prima vista, questa definizione sembra piuttosto superflua; non è solo dicendo che un’istanza di qualsiasi tipo T è convertibile in … beh, a T? Sì. Ma ipotizzo * che lo scopo di questa definizione sia di fornire alle specifiche il vocabolario appropriato per utilizzare il concetto di identity framework di tipo nel contesto della discussione delle conversioni .

Ciò consente di affermazioni sulle conversioni che sono essenzialmente di natura transitiva. Il primo punto che hai citato dalle specifiche come esempio di una dichiarazione tautologica rientra in questa categoria. Dice che se una conversione implicita è definita per un certo tipo (lo chiamo K) ad un altro tipo T 0 e T 0 ha una conversione di identity framework in T, allora K è implicitamente convertibile in T. Dalla definizione di conversione di identity framework data sopra, “ha una conversione dell’id quadro in” in realtà significa “è lo stesso tipo di”. Quindi la dichiarazione è ridondante .

Ma ancora: la definizione della conversione dell’identity framework esiste in primo luogo per equipaggiare le specifiche con un linguaggio formale per descrivere le conversioni senza dover dire cose come “se T 0 e T sono realmente dello stesso tipo”.

OK, tempo per esempi concreti.

Dove l’esistenza di una conversione implicita potrebbe non essere ovvia per alcuni sviluppatori

Nota: un esempio molto migliore è stato fornito da Eric Lippert nella sua risposta alla domanda . Lascio questi primi due esempi come piccoli rinforzi del mio punto. Ho anche aggiunto un terzo esempio che concretizza la conversione di id quadro esistente tra object e dynamic come indicato nella risposta di Eric.

Conversione transitoria di riferimento

Diciamo che hai due tipi, M e N , e hai una conversione implicita definita in questo modo:

 public static implicit operator M(N n); 

Quindi puoi scrivere un codice come questo:

 N n = new N(); M m = n; 

Ora diciamo che hai un file con questa istruzione using in alto:

 using K = M; 

E poi hai, più tardi nel file:

 N n = new N(); K k = n; 

OK, prima di procedere, mi rendo conto che questo è ovvio per te e me . Ma la mia risposta è, ed è stato sin dall’inizio, che potrebbe non essere ovvio per tutti , e quindi specificarlo – mentre è ridondante – ha uno scopo .

Quello scopo è: chiarire a chiunque grattarsi la testa, guardando quel codice, è legale. Esiste una conversione implicita da N a M e una conversione di id quadro esiste da M a K (cioè, M e K sono dello stesso tipo); quindi esiste una conversione implicita da N a K. Non è solo logica (sebbene possa essere logica); è proprio lì nelle specifiche . Altrimenti si potrebbe erroneamente credere che qualcosa del genere sarebbe necessario:

 K k = (M)n; 

Chiaramente, non lo è.

Conversione transitiva di boxe

Oppure prendi il tipo int . Un int può essere inserito in un IComparable , giusto? Quindi questo è legale:

 int i = 10; IComparable x = i; 

Ora considera questo:

 int i = 10; IComparable x = i; 

Di nuovo, , potrebbe essere ovvio per te, me e il 90% di tutti gli sviluppatori che potrebbero mai incontrarlo. Ma per quella esigua minoranza che non la vede subito: esiste una conversione di boxing da int a IComparable , e una conversione di id quadro esiste da IComparable a IComparable (cioè, IComparable e IComparable sono dello stesso tipo); quindi una conversione di boxe esiste da int a IComparable .

Conversione che coinvolge il tipo dynamic

Prenderò un prestito dal mio esempio di conversione di riferimento sopra e lo modificherò leggermente per illustrare la relazione di id quadro tra object e dynamic nella versione 4.0 delle specifiche.

Diciamo che abbiamo i tipi M e N , e abbiamo definito da qualche parte la seguente conversione implicita:

 public static implicit operator M(N n); 

Quindi il seguente è legale:

 N n = new N(); M m = n; 

Chiaramente, quanto sopra è molto meno ovvio rispetto ai due esempi precedenti. Ma ecco la domanda da un milione di dollari: sarebbe ancora legale, anche se gli estratti dalla specifica citata nella domanda non esistessero? (Vado a chiamare questi estratti Q per brevità.) Se la risposta è sì, allora Q è in realtà ridondante. Se no, allora non lo è.

Credo che la risposta sia sì.

Considera la definizione di conversione dell’id quadro , definita nella sezione 6.1.1 (Sto includendo l’intera sezione qui perché è piuttosto breve):

Una conversione di id quadro converte da qualsiasi tipo allo stesso tipo. Questa conversione esiste in modo tale che un’entity framework che ha già un tipo richiesto può essere detta convertibile in quel tipo.

Poiché object e dynamic sono considerati equivalenti, esiste una conversione di id quadro tra object e dynamic , e tra tipi costruiti che sono gli stessi quando si sostituiscono tutte le occorrenze di object dynamic con object . [enfasi mia]

(Quest’ultima parte è anche inclusa nella sezione 4.7, che definisce il tipo dynamic ).

Ora guardiamo di nuovo il codice. In particolare sono interessato a questa linea:

 M m = n; 

La legalità di questa affermazione (ignorando Q – ricorda, il problema che si sta discutendo è l’ipotetica legalità della suddetta affermazione se Q non esistesse), poiché M e N sono tipi personalizzati, dipende dall’esistenza di un utente conversione implicita definita tra N e M .

Esiste una conversione implicita da N a M . Dalla sezione delle specifiche sopra citate, esiste una conversione di id quadro tra M e M . Con la definizione di conversione dell’id quadro , M e M sono dello stesso tipo .

Quindi, proprio come nei primi due (più ovvi) esempi, credo sia vero che esiste una conversione implicita da N a M anche senza tenere conto di Q , proprio come è vero che esiste una conversione implicita da N a K nel primo esempio e che esiste una conversione di boxe da int a IComparable nel secondo esempio.

Senza Q , questo è molto meno ovvio (da qui l’esistenza di Q ); ma ciò non lo rende falso (cioè, Q non è necessario per definire questo comportamento). Rende solo meno ovvio.

Conclusione

Ho detto nella mia risposta originale che questa è la spiegazione “ovvia”, perché mi sembrava che stavi abbaiando dall’albero sbagliato. Inizialmente hai posto questa sfida:

Puoi dare un esempio di due tipi T 1 , T 2 in modo tale che T 1 non sia implicitamente convertibile in T 2 se non fosse per i paragrafi sopra citati?

Nessuno incontrerà questa sfida, Timwi, perché è imansible. Prendi il primo estratto delle conversioni di riferimento. Sta dicendo che un tipo K è implicitamente convertibile in un tipo T se è implicitamente convertibile in T 0 e T 0 è lo stesso di T. Decostruisci questo, rimettilo insieme e ti rimane una ovvia tautologia: K è implicitamente convertibile in T se è implicitamente convertibile in T. Ciò introduce nuove conversioni implicite? Ovviamente no.

Quindi forse il commento di Ben Voigt era corretto; forse questi punti che stai chiedendo sarebbero stati messi meglio in note a piè di pagina, piuttosto che nel corpo del testo. In ogni caso, è chiaro per me che sono ridondanti, e quindi per iniziare con la premessa non possono essere ridondanti, altrimenti non sarebbero lì per imbarcarsi in una folle commissione. Siate disposti ad accettare che una dichiarazione ridondante possa ancora gettare luce su un concetto che potrebbe non essere ovvio per tutti, e diventerà più facile accettare queste affermazioni per quello che sono.

Ridondante? Sì. Tautologico? Sì. Inutile? Secondo me , no.

* Ovviamente, non ho avuto alcuna parte nello scrivere le specifiche del linguaggio C #. Se lo facessi, questa risposta sarebbe molto più autorevole. Così com’è, rappresenta semplicemente il debole tentativo di un ragazzo di dare un senso a un documento piuttosto complesso.


Risposta originale

Penso che tu stia (forse intenzionalmente) trascurando la risposta più ovvia qui.

Considera queste due frasi nella tua domanda:

(1) Inizialmente sembrano ridondanti (tautologhi). (2) Ma devono essere lì per uno scopo, quindi perché sono lì?

Per me, l’implicazione di queste due frasi insieme è che una dichiarazione tautologica non ha alcuno scopo. Ma solo perché una dichiarazione segue logicamente dai presupposti stabiliti, ciò non lo rende evidente a tutti. In altre parole, anche se (1) è vero, la risposta a (2) può essere semplicemente: rendere chiaro il comportamento descritto a chiunque legga le specifiche .

Ora si potrebbe obiettare che anche se qualcosa non è ovvio , non appartiene ancora a una specifica se fornisce una definizione ridondante. A questa potenziale obiezione, posso solo dire: essere realistici. Non è molto pratico (a mio avviso) sfogliare un documento estrapolando tutte le affermazioni che stanno semplicemente affermando fatti che potrebbero essere stati dedotti da dichiarazioni precedenti.

Se questa fosse una pratica comune, penso che troveresti molta letteratura là fuori – non solo specifiche, ma documenti di ricerca, articoli, libri di testo, ecc. – sarebbe molto più breve, più densa e più difficile da capire .

Quindi: sì, forse sono ridondanti. Ma ciò non annulla il loro scopo.

Una conversione di id quadro converte da qualsiasi tipo allo stesso tipo . Questa conversione esiste in modo tale che un’entity framework che ha già un tipo richiesto può essere detta convertibile in quel tipo.

Questo dice che in C # -land, 1 == 1; una vanga è una vanga. Questa è la base per assegnare un riferimento a un object ad una variabile dello stesso tipo; se una variabile T1 digitata e una T2 digitata sono in realtà entrambe le Picche, è ansible assegnarne una all’altra senza doverne esplicitamente lanciare una come Spade. Immagina una variante C # in cui i compiti dovessero assomigliare a questo:

 Spade mySpade = new Spade(); Spade mySpade2; mySpade2 = (Spade)mySpade; //explicit cast required 

Inoltre, una “id quadro” in matematica afferma che un’espressione che valuta un risultato dato un insieme di input equivale a un’altra espressione che produce lo stesso risultato dato gli stessi input. Nella programmazione, ciò significa che un’espressione o una funzione che valuta un’istanza di un tipo è equivalente a quel tipo, senza conversione esplicita. Se non fosse valido, sarebbe richiesto il seguente codice:

 public int myMethod() { /*Do something*/ } ... int myInt = (int)myMethod(); //required even though myMethod() evals to an int. ... int myInt = (int)(1 + 2); //required even though 1, 2, and 1+2 eval to an int. 

La seconda regola dice fondamentalmente che un tipo di valore può essere assegnato a una variabile membro su una class se, in parte, la variabile membro (un tipo inscatolato per definizione, poiché il suo contenitore è un tipo di riferimento) è dichiarata essere dello stesso tipo. Se questa regola non fosse specificata, teoricamente, potrebbe esistere una versione di C # in cui i tipi di puro valore dovrebbero essere convertiti esplicitamente al loro analogo di riferimento per poter essere memorizzati come variabili membro su una class. Immaginate, ad esempio, una versione di C # in cui i tipi di parole chiave blu (int, float, decimal) e i nomi di classi blu chiaro (Int32, Float, Decimal) fanno riferimento a due tipi molto diversi, solo esplicitamente convertibili e int , float, decimal, ecc. non erano legali come tipi di variabili membro perché non erano tipi di riferimento:

 public class MyClass { Int32 MyBoxedValueType; //using "int" not legal } ... MyClass myClass = new MyClass(); int theInt = 2; myClass.MyBoxedValueType = (Int32)theInt; //explicit cast required 

So che sembra sciocco, ma a un certo livello, queste cose devono essere conosciute, e nei computer, devi specificare TUTTO. Leggi il regolamento di Hockey USA per l’hockey su ghiaccio a volte; la prima regola del libro è che il gioco deve essere giocato su una superficie ghiacciata. È uno dei migliori “well duh”, ma anche una verità fondamentale del gioco che deve essere compresa affinché ogni altra regola abbia senso.

È ansible che il codice garantisca il pass-through quando viene chiamato come Convert.ChangeType(client, typeof(Client)) indipendentemente dal fatto che IConvertible sia implementato.

Cerca nell’origine di ChangeType da mscorlib con Reflector e nota le condizioni in cui il value viene restituito così com’è.

Ricorda che a = operatore non è una conversione, solo un set di riferimento. Quindi codice come Client client_2 = client_1 non esegue conversioni implicite. Se viene dichiarata una conversione implicita dell’identity framework, viene emesso l’errore CS0555 .

Immagino che le specifiche dicano che il compilatore C # gestisca tali casi e quindi non puntino manualmente a definire le conversioni di id quadro.