2012-11-21 17 views
6

La domanda sollevata nella mia mente dopo il thread Is it possible to access a reference of a struct from a List to make changes? da reza.Qual è la differenza tra l'elenco <T> e gli indicizzatori di array?

Così, si consideri il seguente struct e interface (sicuramente non molto utile, ma solo per mostrare il problema):

public interface IChangeStruct 
{ 
    int Value { get; } 
    void Change(int value); 
} 

public struct MyStruct : IChangeStruct 
{ 
    int value; 

    public MyStruct(int _value) 
    { 
     value = _value; 
    } 

    public int Value 
    { 
     get 
     { 
      return value; 
     } 
    } 

    public void Change(int value) 
    { 
     this.value = value; 
    } 
} 

MyStruct implementa IChangeStruct, in modo che possiamo cambiare una copia in scatola di esso proprio nel mucchio senza unboxing e sostituendolo con uno nuovo. Questo può essere demostrated con il seguente codice:

MyStruct[] l1 = new MyStruct[] 
{ 
    new MyStruct(0) 
}; 

Console.WriteLine(l1[0].Value); //0 
l1[0].Change(10); 
Console.WriteLine(l1[0].Value); //10 

Ora, cambiamo array per List<T>, vale a dire:

List<MyStruct> l2 = new List<MyStruct> 
{ 
    new MyStruct(0) 
}; 

Console.WriteLine(l2[0].Value); //0 
l2[0].Change(10); 
Console.WriteLine(l2[0].Value); //also 0 

Per quanto ho capito, nel primo caso l1[0] restituito il referense alla scatola struct, mentre nel secondo - era smth altro.

Ho anche cercato di smontare questo ed ho trovato:

1) Per MyStruct[]:

IL_0030: ldelema Utils.MyStruct 
IL_0035: ldc.i4.s 10 
IL_0037: call  instance void Utils.MyStruct::Change(int32) 

2) Per List<MyStruct>:

IL_007c: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<valuetype Utils.MyStruct>::get_Item(int32) 
IL_0081: stloc.s CS$0$0001 
IL_0083: ldloca.s CS$0$0001 
IL_0085: ldc.i4.s 10 
IL_0087: call  instance void Utils.MyStruct::Change(int32) 

Ma mi sembrava essere non è pronto a interpretarlo bene.

Quindi, cosa ha restituito il List<T>? O in che modo array e List<T> restituiscono gli elementi per indice? O è questo solo il caso con i tipi di valore e non ha nulla a che fare con i tipi di riferimento?

P.S .: Io faccio capire che uno non deve cambiamento un'istanza tipo di valore, ma il problema descritto mi ha fatto capire, non ho mai capito quanto List<T> e il lavoro array.

risposta

9

. Net può localizzare gli elementi dell'array sul posto, utilizzando l'istruzione ldelema (indirizzo di caricamento dell'elemento di matrice).

Ciò consente di operare direttamente sugli elementi dell'array senza copiarli. (questo è anche il motivo per cui è possibile passare un elemento dell'array come parametro ref o out)

List<T> non ha tale capacità. Invece, list[i] è solo zucchero sintattico per list.get_Item(i), che è una normale chiamata di metodo che restituisce una copia della struttura.

+1

Gli array hanno questa capacità solo se indicizzati 0 e dimensionali singoli anche se corretti? (poiché 'ldelema' può funzionare solo con tali restrizioni) – Earlz

3

L'indicizzatore di un array rende un elemento disponibile per il codice seguente in modo simile al passaggio come parametro ref. Nessun meccanismo esiste in alcun linguaggio .net per qualsiasi altro tipo di comportarsi allo stesso modo. Qualsiasi altro tipo che consente l'accesso indicizzato deve esporre un paio di metodi, uno dei quali rende disponibile una copia dei dati memorizzati internamente al codice del chiamante, e uno dei quali, data una copia di alcuni dati dal codice del chiamante, memorizza che i dati in qualche modo.Questa limitazione è più visibile con tipi di valore, ma in alcuni casi può anche essere problematico con tipi di riferimento (ad esempio è possibile eseguire un Interlocked.ComapreExchange su un elemento in un T[], ma non su un elemento con un List<T>).

Se uno sta progettando i propri tipi di raccolta, uno può facilitare la limitazione sugli indicizzatori offrendo un membro ActOnItem, consentendo così il codice come MyFancyList.ActOnItem(4, (ref Point it) => {it.X += 4;});. Può essere utile per fornire una famiglia di versioni generiche con un diverso numero di ulteriori ref parametri che sarebbero passati attraverso dal chiamante (ad esempio MyFancyList.ActOnItem(4, (ref MyThing it, ref Thing newValue, ref Thing compareValue) => Threading.Interlocked.CompareExchange(ref it, newValue, compareValue);) dal momento che l'uso di tali metodi può evitare la necessità di lambda di utilizzare le variabili catturati.

Problemi correlati