2015-08-10 8 views
9

Betrachten wir den folgenden Code:Sollten .NET-Strings wirklich als unveränderlich betrachtet werden?

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" 
} 

dies ein Zeiger auf das erste Zeichen des foo erzeugt, neu zuweist es wandelbar zu werden, und ändert die Zeichen 8 und 9-Positionen bis ' '.

Hinweis Ich habe nie wirklich neu zugewiesen foo; stattdessen änderte ich seinen Wert, indem ich seinen Zustand änderte, oder muting die Zeichenfolge. Daher sind .NET-Zeichenfolgen veränderbar.

Das funktioniert so gut, in der Tat, dass der folgende Code:

unsafe 
{ 
    string bar = "Watch this"; 

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

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

"Catch this" aufgrund Stringliteral interning gedruckt wird.

Dies hat viel anwendbar verwendet zum Beispiel dieses:

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 
} 

Dies könnte sparen Speicherzuweisung/Leistungskosten potenziell große, wenn Sie in einem drehzahl- arbeiten:

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); 
} 

ersetzt wird kritisches Feld (z. B. Kodierungen).

Ich denke, Sie könnten sagen, dass dies nicht zählt, weil es "einen Hack verwendet", um Zeiger änderbar zu machen, aber wiederum waren es die C# -Sprachdesigner, die die Zuweisung eines Strings zu einem Zeiger an erster Stelle unterstützten. (In der Tat ist dieser allthetime intern getan in String und StringBuilder, so technisch könnten Sie Ihren eigenen String mit dieser machen.)

So soll .NET wirklich Strings unveränderlich angesehen werden?

+0

Sie sind bei Verwendung der öffentlichen API unveränderlich. Wenn Sie unsicheren Code oder eine falsche Reflexion verwenden, um diese öffentliche API zu umgehen, sind sie es nicht. – MarcinJuraszek

+0

@MarcinJuraszek Zeiger * sind * Teil der öffentlichen API, siehe auch meinen letzten Absatz. –

+1

Ich spreche über öffentliche API der 'string' Klasse - die Methoden, Eigenschaften, die es zur Verfügung stellt. – MarcinJuraszek

Antwort

6

§ 18.6 der Sprache C# Spezifikation (Die fixed Anweisung) richtet sich speziell an den Fall eines Strings durch einen festen Zeiger ändern, und zeigt an, dass dies zu undefinierten Verhalten führen können: Objekte

Ändern des verwalteten Typs über feste Zeiger kann zu undefiniertem Verhalten führen. Da beispielsweise Strings unveränderbar sind, liegt es in der Verantwortung des Programmierers sicherzustellen, dass die Zeichen, auf die ein Zeiger auf eine feste Zeichenfolge verweist, nicht geändert werden. um zu bestätigen, ob die Adressen von Stringliteral weisen in die gleiche Speicherstelle

+0

Interessant, ich habe nur den Begriff "undefined Verhalten" in den C/C++ - Spezifikationen (die ganze Zeit) gehört. Das Sehen in C# ist etwas Neues. –

+1

@JamesKo Es gibt sogar eine Instanz von undefiniertem Verhalten in der C# -Spezifikation, die sich nicht auf "unsicheren" Code bezieht (der einzige, den ich bei einer Schnellsuche finden konnte): wenn Sie benutzerdefinierte awariters mit 'async' /' await' verwenden , und Ihr angepasster Warter ruft die Fortsetzung mehrfach auf, das Verhalten ist nicht definiert. – hvd

+0

* Dies ist der Grund, warum Sie diese Frage ablehnen sollten ...die Antwort ist explizit in der Sprachspezifikation enthalten und wurde anscheinend von den Autoren als so wahrscheinlich angesehen, sie dachten schon lange daran –

1

Ich hatte gerade mit diesem und Experiment zu spielen.

Die Ergebnisse sind:

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 

Wir sehen, dass foo einen Stringliteral initialisiert, die in meinem PC 0x02b215f8 Speicheradresse verweist. Das Zuweisen desselben Zeichenfolgenliterals zu foo2 verweist auf die gleiche Speicheradresse. Und das Erstellen einer Kopie desselben Zeichenkettenliterals macht eine neue.Weitere Tests über string.ReferenceEquals() zeigen, dass sie für foo und foo2 tatsächlich gleich sind, während sie für foo und fooCopy unterschiedliche Referenzen sind.

Es ist interessant zu sehen, wie String-Literale kann im Speicher manipuliert werden und wirkt sich auf andere Variablen, die nur darauf verweisen. Eines der Dinge, auf die wir achten sollten, da dieses Verhalten existiert.

Verwandte Themen