2015-08-10 13 views
9

Si consideri il seguente codice:Le stringhe .NET potrebbero essere considerate immutabili?

unsafe 
{ 
    string foo = string.Copy("This can't change"); 

    fixed (char* ptr = foo) 
    { 
     char* pFoo = ptr; 
     pFoo[8] = pFoo[9] = ' '; 
    } 

    Console.WriteLine(foo); // "This can change" 
} 

Questo crea un puntatore al primo carattere di foo, riassegna a diventare mutabile, e modifica i caratteri 8 e 9 posizioni fino a ' '.

Avviso In realtà non ho mai riassegnato foo; invece, ho cambiato il suo valore modificando il suo stato, o mutando la stringa. Pertanto, le stringhe .NET sono modificabili.

Questo funziona così bene, infatti, che il seguente codice:

unsafe 
{ 
    string bar = "Watch this"; 

    fixed (char* p = bar) 
    { 
     char* pBar = p; 
     pBar[0] = 'C'; 
    } 

    string baz = "Watch this"; 
    Console.WriteLine(baz); // Unrelated, right? 
} 

stamperà "Catch this" a causa di stringa internato letterale.

Questo ha un sacco di usi applicabili, ad esempio questo:

string GetForInputData(byte[] inputData) 
{ 
    // allocate a mutable buffer... 
    char[] buffer = new char[inputData.Length]; 

    // fill the buffer with input data 

    // ...and a string to return 
    return new string(buffer); 
} 

viene sostituito da:

string GetForInputData(byte[] inputData) 
{ 
    // allocate a string to return 
    string result = new string('\0', inputData.Length); 

    fixed (char* ptr = result) 
    { 
     // fill the result with input data 
    } 

    return result; // return it 
} 

Questo potrebbe salvare potenzialmente enormi costi di allocazione della memoria/prestazioni se si lavora in un velocità- campo critico (ad es. codifiche).

Immagino si possa dire che questo non conta perché "usa un hack" per rendere i puntatori mutabili, ma poi di nuovo sono stati i progettisti di linguaggio C# che hanno supportato l'assegnazione di una stringa a un puntatore in primo luogo. (In realtà, questo è fatto allthetime internamente nel String e StringBuilder, quindi tecnicamente si può fare il vostro proprio StringBuilder con questo.)

Quindi, dovrebbe NET stringhe davvero essere considerati immutabili?

+0

Sono immutabili quando si utilizza l'API pubblica. Se si utilizza codice o riflessione non sicuri per ignorare tale API pubblica non lo sono. – MarcinJuraszek

+0

@MarcinJuraszek I puntatori * sono * parte dell'API pubblica, vedi anche il mio ultimo paragrafo. –

+1

Sto parlando dell'API pubblica della classe 'string' - i metodi, le proprietà che espone. – MarcinJuraszek

risposta

6

§ 18.6 delle specifiche C# lingua (Il fixed dichiarazione) affronta in modo specifico il caso di modificare una stringa tramite un puntatore fissa, e indica che così facendo può portare a un comportamento indefinito:

oggetti Modificare di tipo gestito tramite puntatori fissi può comportare un comportamento non definito. Ad esempio, poiché le stringhe sono immutabili, è responsabilità del programmatore assicurarsi che i caratteri referenziati da un puntatore a una stringa fissa non vengano modificati.

+0

Interessante, ho sempre sentito il termine "comportamento indefinito" usato nelle specifiche C/C++ (sempre). Vedendolo in C# è qualcosa di nuovo. –

+1

@JamesKo Esiste anche un'istanza di comportamento non definito nella specifica C# che non si riferisce al codice 'non sicuro '(l'unico che ho potuto trovare in una ricerca rapida): se si usano gli attenditori personalizzati con' async'/'await' e il tuo attendente personalizzato invoca la continuazione più volte, il comportamento non è definito. – hvd

+0

* Questo * è il motivo per cui sottoponiamo questa domanda a downvote ...la risposta è esplicitamente trattata nelle specifiche del linguaggio, ed è stato apparentemente considerato dagli autori come un motivo di preoccupazione, hanno pensato bene prima del tempo –

1

ho dovuto giocare con questo e esperimento per confermare se gli indirizzi dei stringa letterale siano rivolte nella stessa posizione di memoria.

I risultati sono:

string foo = "Fix value?"; //New address: 0x02b215f8 
string foo2 = "Fix value?"; //Points to same address: 0x02b215f8 
string fooCopy = string.Copy(foo); //New address: 0x021b2888 

fixed (char* p = foo) 
{ 
    p[9] = '!'; 
} 

Console.WriteLine(foo); 
Console.WriteLine(foo2); 
Console.WriteLine(fooCopy); 

//Reference is equal, which means refering to same memory address 
Console.WriteLine(string.ReferenceEquals(foo, foo2)); //true 

//Reference is not equal, which creates another string in new memory address 
Console.WriteLine(string.ReferenceEquals(foo, fooCopy)); //false 

vediamo che foo inizializza una stringa letterale che punta a 0x02b215f8 indirizzo di memoria nel mio PC. Assegnare la stessa stringa letterale a foo2 fa riferimento allo stesso indirizzo di memoria. E creare una copia della stessa stringa letterale ne crea una nuova.Ulteriori test via string.ReferenceEquals() rivelano che sono effettivamente uguali per foo e foo2 mentre diverso riferimento per foo e fooCopy.

È interessante notare come i valori di stringa possono essere manipolati in memoria e influire su altre variabili che fanno semplicemente riferimento a esso. Una delle cose su cui dovremmo stare attenti poiché questo comportamento esiste.

Problemi correlati