2009-07-24 11 views
42

Indagando un bug, ho scoperto che era a causa di questa stranezza in C#:Perché il mio array C# perde le informazioni sul segno di tipo quando si esegue il cast sull'oggetto?

sbyte[] foo = new sbyte[10]; 
object bar = foo; 
Console.WriteLine("{0} {1} {2} {3}", 
     foo is sbyte[], foo is byte[], bar is sbyte[], bar is byte[]); 

L'uscita è "Vero Falso Vero Vero", mentre io sarei aspettato "bar is byte[]" per restituire false. Apparentemente la barra è sia una byte[] sia una sbyte[]? Lo stesso accade per altri tipi firmati/non firmati come Int32[] vs UInt32[], ma non per dire Int32[] vs Int64[].

Qualcuno può spiegare questo comportamento? Questo è in .NET 3.5.

risposta

65

UPDATE: Ho usato questa domanda come base per un blog, qui:

http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx

Vedere i commenti del blog per una discussione estesa di questa edizione. Grazie per la bella domanda!


Hai inciampato attraverso una contraddizione interessante e sfortunato tra il sistema di tipo CLI e il sistema C# tipo.

CLI ha il concetto di "compatibilità di assegnazione". Se un valore x del tipo di dati noto S è "assegnazione compatibile" con una particolare posizione di memoria y del tipo di dati noto T, allora è possibile memorizzare x in y. In caso contrario, fare ciò non è un codice verificabile e il verificatore lo rifiuterà.

Il sistema di tipo CLI dice, ad esempio, che i sottotipi di tipo di riferimento sono compatibili con i supertipi di tipo di riferimento. Se si dispone di una stringa, è possibile memorizzarla in una variabile di tipo oggetto, poiché entrambi sono tipi di riferimento e la stringa è un sottotipo di oggetto. Ma il contrario non è vero; i supertipi non sono compiti compatibili con i sottotipi. Non puoi incollare qualcosa che solo noto per essere oggetto in una variabile di tipo string senza prima lanciarlo.

Fondamentalmente "assegnazione compatibile" significa "ha senso inserire questi bit esatti in questa variabile". L'assegnazione dal valore di origine alla variabile di destinazione deve essere "preservazione della rappresentazione". Vedere il mio articolo su questo per i dettagli:

http://ericlippert.com/2009/03/03/representation-and-identity/

Una delle regole della CLI è "se X è compatibile con l'assegnazione Y, allora X [] è compatibile con l'assegnazione Y []".

In altre parole, gli array sono covarianti rispetto alla compatibilità dell'assegnazione. Questo è in realtà un tipo di covarianza interrotto; vedere il mio articolo su questo per i dettagli.

http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

che non è una regola di C#. La regola di covarianza dell'array di C# è "se X è un tipo di riferimento convertibile implicitamente al tipo di riferimento Y, quindi X [] è implicitamente convertibile in Y []". Questa è una regola sottilmente diversa, e quindi la tua situazione confusa.

Nella CLI, uint e int sono compatibili dell'assegnazione. Ma in C#, la conversione tra int e uint è ESPLICITA, non IMPLICIT, e questi sono tipi di valore, non tipi di riferimento. Quindi in C# non è legale convertire un [] in un uint [].

Ma è legale nella CLI. Quindi ora ci troviamo di fronte a una scelta.

1) L'implementazione "è" in modo che quando il compilatore non è in grado di determinare la risposta staticamente, in realtà chiama un metodo che controlla tutte le regole C# per la convertibilità che preserva l'identità. Questo è lento e il 99,9% delle volte corrisponde a quello che sono le regole CLR. Ma prendiamo il colpo di prestazioni in modo da essere al 100% conformi alle regole di C#.

2) Implementare "è" in modo tale che quando il compilatore non è in grado di determinare la risposta in modo statico, esegue il controllo di compatibilità dell'assegnazione CLR incredibilmente veloce e convive con ciò che un uint [] è un int [], anche se in realtà non sarebbe legale in C#.

Abbiamo scelto quest'ultimo. È un peccato che C# e le specifiche CLI non siano d'accordo su questo punto, ma siamo disposti a convivere con l'incoerenza.

+0

Ottima lettura. Grazie per la risposta. –

+2

Ciao Eric, per curiosità, voi ragazzi avete deciso di accettare questa incoerenza o non era prevista prima? Mi stavo solo chiedendo. –

+0

Cancellato il mio post in ossequio a una risposta molto migliore e approfondita. – LBushkin

0

Sicuramente l'uscita è corretta. bar "è" sia sbyte [] che byte [], perché è compatibile con entrambi, poiché la barra è semplicemente un oggetto, quindi "potrebbe essere" o firmata o non firmata.

"è" è definito come "l'espressione può essere inoltrata per digitare".

+1

Ma poiché 'bar' è del tipo' object', il tipo di base di qualsiasi altro tipo, che significa che si può dire 'bar è ' e restituisce true, e che non è il Astuccio. Perché puoi lanciare un 'sbyte []' a 'byte []' solo perché capita di passare da un tipo di riferimento? –

9

Ran frammento attraverso riflettore:

sbyte[] foo = new sbyte[10]; 
object bar = foo; 
Console.WriteLine("{0} {1} {2} {3}", new object[] { foo != null, false, bar is sbyte[], bar is byte[] }); 

Il compilatore C# sta ottimizzando i primi due confronti (foo is sbyte[] e foo is byte[]). Come puoi vedere, sono stati ottimizzati per foo != null e semplicemente sempre false.

5

anche interessante:

sbyte[] foo = new sbyte[] { -1 }; 
    var x = foo as byte[]; // doesn't compile 
    object bar = foo; 
    var f = bar as byte[]; // succeeds 
    var g = f[0];    // g = 255 
+0

Mi manca qualcosa qui. Non è questo che ti aspetti. Qual è la stranezza? – cdm9002

+0

Non che 'g = 255', che è previsto, ma che' bar come byte [] 'non restituisce nulla. –

+2

Giusto - così ora che hai letto la mia risposta, puoi dedurre che lo stesso tipo di cosa sta accadendo qui. Con il primo, sappiamo al momento della compilazione che questo viola le regole di C#. Con il secondo, non lo sappiamo. Quindi dobbiamo (1) emettere un metodo che implementa tutte le regole del linguaggio C# per fare il cast, o (2) usare le regole del cast di CLR, che sono sottilmente diverse dalle regole di C# per una piccola percentuale di casi bizzarri. Abbiamo scelto (2). –

Problemi correlati