2015-02-02 17 views
18

Si consideri il seguente codice C#:In C#, perché il dizionario [0] ++ funziona?

var d = new Dictionary<int, int>(); 
d[0] = 0; 
d[0]++; 

Qual è il valore di d [0] dopo questo codice viene eseguito? Mi aspetterei d [0] == 0, poiché la proprietà Item del dizionario < restituisce un valore tipoint, presumibilmente nello stack, che viene quindi incrementato. Sorprendentemente, però, quando in realtà esegue questo codice a trovare d [0] == 1.

L'esempio precedente si comporta come se l'indicizzatore sta tornando un tipo di riferimento, ma ora considerare quanto segue:

var d = new Dictionary<int, int>(); 
d[0] = 0; 
var a = d[0]; 
a++; 

Qual è il valore di d [0] dopo l'esecuzione di questo codice? Questa volta otteniamo d [0] == 0 come previsto, quindi l'indicizzatore non restituirà sicuramente un riferimento.

Qualcuno sa perché vediamo questo comportamento?

+13

Forse d [0] ++, si traduce per d [0] = d [0] + 1? –

+4

Il compilatore non risolve 'd [0] ++' a 'd [0] = d [0] + 1'? Lo spiegherebbe se così fosse. – Octopoid

+3

Il dizionario __stores__ questi numeri interi. Puoi incrementarli e puoi estrarli. Niente di strano lì, davvero. - Sono __are__ tipi di valore, che significa qui, che non puoi creare riferimenti ad essi come sembra provare il tuo secondo esempio .. – TaW

risposta

17

C# Specifica 7.6.9 Postfix increment and decrement operators:

L'elaborazione runtime di un incremento suffisso o decremento funzionamento del modulo x ++ o x-- costituito dai seguenti passaggi:

  • se x è classificata come accesso a proprietà o indicizzatore:
    • L'espressione di istanza (se x non è statico) e l'elenco di argomenti (se x è un accesso dell'indicizzatore) associato a x vengono valutati e il resu I lts sono usati nelle successive chiamate get e set accessor.
    • Viene richiamato l'accesso get di x e il valore restituito viene salvato.
    • L'operatore selezionato viene richiamato con il valore salvato di x come argomento.
    • L'accessor set di x viene richiamato con il valore restituito dall'operatore come argomento del valore.
    • Il valore salvato di x diventa il risultato dell'operazione.

E questo in realtà non hanno nulla a che fare con il tipo di valore vs.tipo di riferimento semantico, come -- e ++ non dovrebbe cambiare istanza ma restituire nuova istanza con un nuovo valore.

public static class Test { 
    public static void Main() { 
     TestReferenceType(); 
     TestValueType(); 
    } 
    public static void TestReferenceType() { 
     var d=new Dictionary<int,BoxedInt>(); 
     BoxedInt a=0; 
     d[0]=a; 
     d[0]++; 
     d[1]=2; 
     BoxedInt b=d[1]; 
     b++; 
     Console.WriteLine("{0}:{1}:{2}:{3}",a,d[0],d[1],b); 
    } 
    public static void TestValueType() { 
     var d=new Dictionary<int,int>(); 
     int a=0; 
     d[0]=a; 
     d[0]++; 
     d[1]=2; 
     int b=d[1]; 
     b++; 
     Console.WriteLine("{0}:{1}:{2}:{3}",a,d[0],d[1],b); 
    } 
    public class BoxedInt { 
     public int Value; 
     public BoxedInt(int value) { 
      Value=value; 
     } 
     public override string ToString() { 
      return Value.ToString(); 
     } 
     public static implicit operator BoxedInt(int value) { 
      return new BoxedInt(value); 
     } 
     public static BoxedInt operator++(BoxedInt value) { 
      return new BoxedInt(value.Value+1); 
     } 
    } 
} 

Sia metodo di prova verrà stampata stessa stringa 0:1:2:3. Come puoi vedere, anche con il tipo di riferimento devi chiamare set accessor per osservare il valore aggiornato nel dizionario.

4

Il tuo codice funziona in questo modo perché il l'indicizzatore di d restituisce il valore di riferimento del int (che è un tipo di valore). Il tuo codice è sostanzialmente identica a questa:

var d = new Dictionary<int, int>(); 
d[0] = 0; 
d[0] = d[0] + 1; // 1. Access the indexer of `d` 
       // 2. Increment it (returned another int, which is of course a value type) 
       // 3. Store the new int to d[0] again (d[0] now equals to 1) 

d[0] restituito 0 nel tuo esempio secondo codice è a causa della tipo di valore semantica, in particolare questa linea:

var a = d[0]; // a is copied by value, not a **reference** to d[0], 
      // so they are two separate integers from here 
a++; // a is incremented, but d[0] is not, because they are **two** separate integers 

ho trovato di explanation sulla Jon Skeet la differenza tra tipi di riferimento e tipi di valore estremamente utili.

+0

Se così fosse, allora mi aspetto che il secondo esempio mostri lo stesso comportamento. – Mike

+2

Perché dovrebbe? Stai prendendo una copia dei dati di tipo valore nella tua variabile a - non c'è ragione per cui il valore di d [0] cambierebbe. – Octopoid

+1

Se l'indicizzatore restituiva un tipo di riferimento, la variabile 'a' avrebbe trattenuto (e incrementato) il valore tramite riferimento. Per quanto ne so non c'è modo di restituire un int come un tipo di riferimento, a meno che non lo si box prima via (oggetto) . – Mike

2

Il secondo esempio non funzionerà come credete, perché int non è un tipo di riferimento. Quando estrai il valore dal dizionario stai assegnando una nuova variabile in questo caso a.

Il primo esempio viene compilato a questo:

d[0] = d[0] + 1; 

Tuttavia il secondo esempio viene compilato a questo:

int a = d[0]; 
a++; 

di elaborare su questo. Gli indicizzatori funzionano come proprietà e le proprietà sono funzioni getter/setter. In questo caso, d[0] = chiamerà la proprietà setter dell'indicizzatore. d[0] + 1 chiamerà la proprietà getter dell'indicizzatore.

Un int non è un oggetto di riferimento. Il secondo esempio funzionerebbe con le classi, ma non con gli interi. Proprietà non restituisce un riferimento alla variabile neanche. Se modifichi il valore di ritorno dall'indicizzatore, modifichi una variabile completamente nuova e non quella effettivamente memorizzata nel dizionario. Ecco perché il primo esempio funziona come previsto, ma il secondo no.

Il primo esempio aggiorna nuovamente l'indicizzatore, a differenza del secondo.

Il secondo esempio funzionerebbe come il primo se lo faceste in questo modo.

int a = d[0]; 
a++; 
d[0] = a; 

Questo è più di un concetto per comprendere le proprietà e indicizzatori.

class MyArray<T> 
{ 
    private T[] array; 

    public MyArray(T[] _array) 
    { 
     array = _array; 
    } 

    public T this[int i] 
    { 
     get { return array[i]; 
     set { array[i] = value; } 
    } 
} 

consideri ora la seguente per un array normale

int[] myArray = new int[] { 0, 1, 2, 3, 4 }; 
int a = myArray[2]; // index 2 is 1 
// a is now 1 
a++; // a is now 2 
// myArray[2] is still 1 

Il codice precedente è così, perché int non è un tipo di riferimento e indicizzatori non restituisce un riferimento in quanto non è passato da ref, ma come valore di ritorno della funzione normale.

Consideriamo ora il seguente per MyArray

var myArray = new MyArray<int>(new int[] { 0, 1, 2, 3, 4 }); 
int a = myArray[2]; // index 2 is 1 
// a is now 1 
a++; // a is now 2 
// myArray[2] is still 1 

Ti aspettavi myArray [2] per creare un riferimento a myArray [2]? Non dovresti. Lo stesso vale per Dictionary

Questa è l'indicizzatore per Dictionary

// System.Collections.Generic.Dictionary<TKey, TValue> 
[__DynamicallyInvokable] 
public TValue this[TKey key] 
{ 
    [__DynamicallyInvokable] 
    get 
    { 
     int num = this.FindEntry(key); 
     if (num >= 0) 
     { 
      return this.entries[num].value; 
     } 
     ThrowHelper.ThrowKeyNotFoundException(); 
     return default(TValue); 
    } 
    [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] 
    set 
    { 
     this.Insert(key, value, false); 
    } 
} 

penso che dovrebbe essere ormai chiaro, il motivo per cui le prime opere di questo tipo fa e perché il secondo non troppo.

+1

Perché è stato downvoted? – Bauss

+0

Non so chi ha downvoted o perché, ma volevo chiarire che il motivo per cui ho fornito il secondo esempio era specificamente per dimostrare che non era un valore di riferimento restituito dall'indicizzatore. – Mike

0

Il bello di generici come il dizionario < TKey, TValue > che si utilizza nell'esempio è che un dizionario specializzato viene generato utilizzando i parametri di tipo specificati. Quando dichiari un dizionario < int, int > la "chiave" e il "valore" che costituiscono KeyValuePair utilizzati dal dizionario sono tipi "int" letterali a.k.a tipi di valori. I valori "int" non sono in scatola. Quindi ...

var d = new Dictionary<int, int>(); 
d[0] = 0; 
d[0]++; 

è funzionalmente equivalente a ...

int i = 0; 
i++;