2013-05-09 13 views
69

Array in C# è co-variante implicitamente sul tipo di riferimento:Perché C# si comporta diversamente su due array di int sintassi

object[] listString = new string[] { "string1", "string2" }; 

Ma non sul tipo di valore, quindi se si cambia string a int, si otterrà errore di compilazione:

object[] listInt = new int[] {0, 1}; // compile error 

Ora, la preoccupazione è quando si dichiara int serie come due sintassi di sotto del quale non dichiarano esplicitamente il tipo int, basta solo differenziarsi sul new[], compilatore tratterà in modo diverso:

object[] list1 = { 0, 1 };  //compile successfully 
object[] list2 = new[] {0, 1}; //compile error 

Otterrete object[] list1 = { 0, 1 }; compilato con successo, ma object[] list2= new[] {0, 1}; errore compilato.

Sembra che il compilatore C# tratta

object[] list1 = { 0, 1 }; 

come

object[] list1 = new object[]{ 0, 1 }; 

ma

object[] list2 = new[] { 0, 1 }; 

come

object[] list2 = new int[]{ 0, 1 }; //error because of co-variant 

Perché il compilatore C# si comporta in modo diverso in questo caso?

+6

+1, vieni da [questa domanda] (http://stackoverflow.com/q/16456507/961113) – Habib

+0

@Habib: sì, ispirato da esso –

+0

Indovina rapidamente, tutti i tipi di riferimento accettano lo stesso numero di byte per il riferimento quindi, la sua conversione facile a implicita. – Jodrell

risposta

57

La versione che compila utilizza un inizializzatore di matrice per inizializzare list1. Il linguaggio C# spec, §1.110 ("initializers Array") afferma:

An array initializer consists of a sequence of variable initializers, enclosed by “{”and “}” tokens and separated by “,” tokens. Each variable initializer is an expression or, in the case of a multi-dimensional array, a nested array initializer.

The context in which an array initializer is used determines the type of the array being initialized. In an array creation expression, the array type immediately precedes the initializer, or is inferred from the expressions in the array initializer. In a field or variable declaration, the array type is the type of the field or variable being declared.

When an array initializer is used in a field or variable declaration, such as:

int[] a = {0, 2, 4, 6, 8}; 

it is simply shorthand for an equivalent array creation expression:

int[] a = new int[] {0, 2, 4, 6, 8}; 

Quindi è ovvio che questo dovrebbe compilare.

La seconda versione utilizza un'espressione di creazione di matrice esplicita, in cui si specifica al compilatore in modo specifico quale tipo di matrice creare. §1.51.10.4 ("Array espressioni di creazione") recita:

An array creation expression of the third form is referred to as an implicitly typed array creation expression. It is similar to the second form, except that the element type of the array is not explicitly given, but determined as the best common type (§1.50.2.14) of the set of expressions in the array initializer.

Pertanto, la seconda versione è equivalente a

object[] list2 = new int[] { 0, 1 }; 

Quindi la questione ora diventa effettivamente "perché non posso assegnare un int[] a un object[] ", proprio come si menziona alla fine della domanda. E la risposta è semplice, dato in §1.109 ("Array covarianza"):

Array covariance specifically does not extend to arrays of value-types. For example, no conversion exists that permits an int[] to be treated as an object[] .

10

Quando si utilizza { e }, si utilizza inizializzatori raccolta (vedi: http://msdn.microsoft.com/en-us/library/vstudio/bb384062.aspx). I valori tra quelle parentesi dovranno essere messi da qualche parte. Pertanto è necessario creare una collezione. Il compilatore analizzerà il contesto per scoprire che tipo di raccolta.

Nel caso in cui il primo: object[] list1 = { 0, 1 }; è chiaro, è necessario creare una raccolta. Ma che tipo dovrebbe essere? Non esiste l'operazione new da qualche parte. C'è solo un suggerimento: list1 è di tipo object[]. Quindi il compilatore crea quella raccolta e la riempie con i valiues.

Nel secondo esempio object[] list1 = new[] { 0, 1 }; c'è un altro suggerimento: new[]. E questo suggerimento dice esplicitamente: ci sarà un array. Quell'array non ha un tipo, quindi cercherà di trovare il tipo dell'array analizzando i valori. Questi sono tutti int quindi creerà un array di int e lo riempirà. L'altro suggerimento object[] è completamente ignorato perché i suggerimenti sulla creazione sono molto più importanti dei suggerimenti su cui dovrebbe essere assegnato. Ora il compilatore vuole assegnare questo array a list1 e BOOM: non va bene!

1

Un inizializzatore di array è un vantaggio del compilatore. Se dico "Sto dichiarando un array di oggetti e assegnandogli un valore", è ragionevole che il compilatore assuma che il tuo { 0, 1 } sia un array di oggetti e lo interpreti come tale. Anche se la sintassi sembra essere un compito, non lo è: stai usando un inizializzatore. L'estensione di questa sintassi è object[] list1 = new object[] { 0, 1 }

Quando dici new[] { 0, 1 }, questa è un'espressione che crea un array e lo inizializza.Questa espressione viene valutata indipendentemente da ciò a cui la si sta assegnando, e poiché il compilatore rileva la digitazione implicita di interi, crea uno int[]. La versione lunga di questa espressione è object[] list2 = new int[] { 0, 1 }

Se si confrontano le versioni a mano lunga di queste due affermazioni, è chiaro per vedere dove si differenziano.

27

La dichiarazione

object[] listInt = new int[] {0, 1}; 

è invalido per conversioni matrice covariante non sono ammessi per tipi di valore (e int è un tipo di valore). In alternativa, la dichiarazione

object[] listInt = new string[] {"0", "1"}; 

è valida poiché le conversioni di array covarianti sono consentite per i tipi di riferimento. Questo perché l'assegnazione x = (object)myString riguarda solo un compito semplice, ma y = (object)myInt richiede un'operazione di box.

Ora sulla differenza tra le due dichiarazioni. Nella dichiarazione object[] list2 = new[] { 0, 1 }, a causa di come funziona l'inferenza di tipo, prima esamina l'espressione lato destro e conclude che new[] { 0, 1 } deve essere trattato come new int[] { 0, 1 }. Quindi tenta di assegnare questo array int a un array di oggetti, dando un errore a causa della conversione covariante del problema dei tipi di valore. La dichiarazione object[] list1 = { 0, 1 }, tuttavia, utilizza un inizializzatore della raccolta e, in tali circostanze, il tipo della raccolta è dove viene definito il tipo, quindi ogni elemento verrà castato per il tipo previsto dalla raccolta.

2

La dichiarazione object[] list1 = { 0, 1 }; compila perché il compilatore è abbastanza intelligente per sapere che si sta tentando di convertire una serie di tipi numerici a un riferimento -tipo array, quindi inserisce gli elementi Int32 in tipi di riferimento.

Si potrebbe anche casella esplicitamente il tipo primitivo:

object[] list2 = Array.ConvertAll<int, Object>(new[] { 0, 1 }, input => (Object)input);

Il compilatore non implicitamente fare la boxe per te quando hai specificato 'int []' o 'Int32 []' come l'array digitare, ma sembra che questo potrebbe essere aggiunto a C#.

1
object[] listInt = new int[] {0, 1}; 

è una scorciatoia per

object[] listInt; 
listInt = new int[] {0, 1}; 

che non funziona perché int[] is not covariant with object[].

E quando si dice new[], è equivalente a new int[], quindi vale lo stesso.

Problemi correlati