Wieso werden Strings bei jeder Modifikation neu angelegt?
-
Hi,
das .NET Konzept zu Strings macht mich bisschen wuschig. Sobald man einen String verändert, z.B. einen zweiten Teilstring anhängt, wird der String komplett neu im Speicher abgelegt. Mir ist klar warum, bei zwei Referenzen macht das durchaus Sinn, zumal der ursprüngliche Speicherbereich sowieso erweitert werden müsste. Referenziert nur eine Variable einen String, wird er natürlich trotzdem neu angelegt, der GC räumt den alten danach ja auf, weil er nicht mehr referenziert wird. Soweit OK.
Aber einen Fall kann ich mir nicht so ganz erklären: Angenommen, es gibt einen String, mit nur einer Referenz, die auf ihn verweist. In diesem String wechsle ich nur ein einzelnes Zeichen aus, trotzdem wird er komplett neu angelegt. Warum? Wäre es zu kostspielig, auf mehrfache Referenzen zu überprüfen, macht man es nur, damit es einheitlich ist, oder hat es andere Gründe?
Danke im Voraus & noch einen schönen Abend,
Entfrickler
-
Hm, es ist natürlich auch noch von Vorteil, dass man zwei Strings anhand der Adressen schon auf gleichheit überprüfen kann.
-
Lies diese kurze Abhandlung zum Thema, bemühe Google zum Thema ".NET immutable strings".
-
Hi,
wir sprechen leider im Alltag allzu oft, auch wenn wir in C# programmieren, über String-VARIABLEN. Das mag zunächst harmlos klingen, aber dahinter steckt der Gedanke, dass eine "String-Variable" ein lokaler Wert ist, den man im Laufe der Programmausführung ändern kann. Für Programmierer, die nicht von einer funktionalen Programmiersprache her kommen, ist die Vorstellung, dass man eine Variable definiert, deren Inhalt man nicht ändern kann, einfach nur seltsam.
Aber Objekte die immutable sind, bringen viele Vorteile mit sich. Man kann diese Objekte über Methoden-, Thread- und Maschinengrenzen hinweg übergeben, ohne sich Sorge um deren Zustand/Identität machen zu müssen, da sie den State quasi mit sich tragen. Wird dieser State geändert, so kann dies nur über die Erstellung einer neuen Instanz erfolgen. Diese read-only Eigenschaft erspart uns viele Bugs und Kopfzerbrechen.
C# hat die Immutabilität von Strings aber nicht vollständig implementiert. Die Sprache hat nur die Immutabilität der String-Werte sichergestellt. In einer perfekt immutablen Welt müsste folgender Code einen Fehler verursachen:
string meinName = "Gantenbein"; meinName = "NichtBisAnsEndeGedacht"; // sollte einen Compiler-Fehler verursachen
DAS wäre nun echte Immutabilität: Kompilierfehler bei der erneuten Zuweisung, nicht nur bei meinName[0] = "g".
Aber die c#-Realität sieht so aus: Strings sind Zeiger auf den gemanagten Heap. Zwei verschiedene Pointer können auf die gleichen Daten zeigen (interning). Die Verwirrung ist zudem groß, da fast alle mutablen Typen im .NET Framework Referenz-Typen sind, und die Mutation eigentlich nur die auf dem Heap alloziierten Daten betrifft.
Ja, Immutabilität sieht zunächst nach Verschwendung aus. Aber man muss Verschwendung näher definieren. Unter .NET sind Speicherzuweisungen sehr schnell und billig, und Speicherfreigaben haben uns nicht zu kümmern, dazu gibt es den Garbage Collector. Unter diesen Umständen und wenn man alle oben zitierten Vorteile der Immutabilität im Auge behält, ist die Immutabilität ein Segen.
Auch gibt es mutable Alternativen. [String].Replace() gibt ein neues Objekt zurück, aber [StringBuilder].Replace() mutiert/modifiziert das vorhandene Objekt (einen internen char buffer). StringBuilder kann in Interop-Szenarien verwendet werden, wo z.B. eine externe C-Funktion den übergebenen StringBuilder-Parameter modifiziert. Und, last but not least, String.Format() erstellt intern seinen eigenen StringBuilder, um das Verketten von immutable Strings zu vermeiden.
-
Als Ergänzug allem hier gesagten:
Wenn man effizient Zeichen in einem String austauschen will, dann kann man das auch, indem man einen modifizierbaren String verwendet. Und der heißt in C#, wer hätte das gedacht,
System.Text.StringBuilder
. Hierbei sollte man sich aus Performancegründen darüber bewusst sein, dass der StringBuilder ein String ist! Es gibt irgendwo in den Tiefen von CodeProject eine vorzügliche Analyse, und das Ergebnis ist eventuell erstaunlich: Die StringBuilder-Klasse wird tatsächlich zu einem ganz normalen (allerdings veränderbaren) String runterkompiliert. Jegliches Verwaltungsoverhead entfällt.
-
Ach so, noch eine Bemerkung zum Thema Interning: Das ist extrem praktisch, wenn man Parser baut. Für effiziente Parser braucht man in der Praxis nämlich immer eine Art String-Pool, weil die andauernden Vergleiche von Bezeichnern sonst ganz ordentlich zu Buche schlagen. Ein Großteil des „Drachenbuchs“ wird von Diskussionen über effiziente Hashing-Strategien eingenommen. In .NET bekommt man all dies gratis dazu, ohne einen Finger zu krümmen (hmm, na gut, ein Aufruf von 'String.Intern').
-
Hallo maro158,
in deiner Ausführung wirst du ziemlich ungenau bis hin zu falsch, da du Variablen, Objekte und Referenzen in einen Topf schmeißt.
Variablen liegen auf dem Stack, immer, entweder es sind direkt ValueTypes und auf dem Stack liegen die entsprechenden Daten oder es sind Reference Types und die Variable ist somit eine Referenz die auf ein Objekt auf dem Heap zeigt (Bitte den Ausdruck Zeiger für Referenzen nicht verwenden da er nicht korrekt ist, Zeiger sind nochmal was anderes). In der Art hast du das ja auch erklärt, wirst dann aber unsauber und die Aussage über "echte" Unveränderlichkeit ist dann einfach nur falsch da du Konstantheit bei Variablen mit Immutablilität gleich setzt.
Der Ausdruck "String Variable" vollkommen korrekt. Eine String Referenz auf dem Stack kann ich jederzeit andere String Objekte vom Heap zuweisen, das hat wie gesagt nichts mit Immutablität zu tun.
maro158 schrieb:
In einer perfekt immutablen Welt müsste folgender Code einen Fehler verursachen...
Eben nicht! Wenn die Variable eine Konstante wäre, dann müsste das nen Fehler geben, aber wie schon gesagt, Stringreferenzen sind so variabel wie jede andere Referenz. Nur wenn du, über den Indexer z.B., die Daten des eigentlichen Objektes ändern willst greift die Immutabilität. Du darfst hier nicht die Referenz auf ein Objekt mit dem Objekt gleichsetzen.
maro158 schrieb:
Die Verwirrung ist zudem groß, da fast alle mutablen Typen im .NET Framework Referenz-Typen sind, und die Mutation eigentlich nur die auf dem Heap alloziierten Daten betrifft.
Naja, du hast dich vielleicht verwirren lassen da du wie gesagt nicht genau unterschieden hast zwischen Referenz und eigentlichen Objekt. Die Referenz aufm Stack ist variabel, das Objekt aufm Heap dagegen nicht, im Prinzip sowas wie konstante Objekte.
-
Hallo Talla,
Du gehst in Deiner Kritik von den C#-Sprachkonzepten aus, ich hingegen blicke mit einem FUNKTIONALEN Auge auf die Sache.
Die Immutabilität als Begriff hat ihren Ursprung in den funktionalen Sprachen und C# übernimmt zu meiner Freude von Version zu Version mehr Features von dort. Sorry, dass ich keine Begriffe wie 'value' statt 'Variable' verwende, aber ich fürchte, die Verwirrung wäre noch größer.
Um das Wesen von Immutabilität begreiflich zu machen, mußte ich das begriffliche Terrain von C# etwas verlassen, aber ich versichere Dir, dass ich sehr wohl zwischen Variablen, Referenzen und Objekten unterscheiden kann. Manchmal ging ich jedoch einen Schritt weiter, wie im Fall von Strings als Zeiger, da ich mich hier auf die Implementation bezog. Das ist pädagogisch nicht korrekt, sonst völlig richtig.
Eine String Referenz auf dem Stack kann ich jederzeit andere String Objekte vom Heap zuweisen, das hat wie gesagt nichts mit Immutablität zu tun.
Vollkommen richtig im ersten Teil. Oh, nur eine Zwischenfrage, hast Du schon mal in einer funktionalen Sprache programmiert?
Dass Du sehr ad literam zwischen Variablen, Referenzen und Objekten unterscheiden kannst, kann keinen wundern. Aber ich vermisse jenseits der begrifflichen Diskussion die Annäherung an den Inhalt der Immutabilität.
Über etwas mehr Substanz würde ich mich sehr freuen. Lass uns reden.
-
Hallo,
Du gehst in Deiner Kritik von den C#-Sprachkonzepten aus
Nein, ich gehe allgemein von OO Sprachen aus, und C# in V 1 war noch alles andere als funktional angehaucht. Ebenso trifft meine Argumentation auf Java und C++ zu, alles Sprachen die schon einige Jahre aufm Buckel haben und gerade Java hat ja so ziemlich nichts von einer funktionalen Sprache. Deshalb wäre es nett wenn du irgend nen Link oder ähnliches hättest wo auf den Ursprung der Immutabilität eingegangen wird. Mir war bisher nicht bekannt das dieses Konzept aus den funktionalen Sprachen stammen soll.
Meine Kenntnisse von funktionalen Sprachen sind nicht arg ausgeprägt, aber bei rein funktionalen Sprachen arbeitet man ja allgemein mit Daten die in vielen Fällen immutable sind. Springen wir mal wieder zu den OOP Sprachen zurück gibts dort Objekte welche die Daten repräsentieren. Wo das Konzept auch herkommen mag, es gibt dort auch immutable Objects, da sind wir uns ja einig und da bestand nie Zweifel. Nun gibts in den OOP Sprachen aber sowas wie Zeiger und Referenzen. Ein Verweis auf dem Stack der auf ein Objekt aufm Heap zeigt. Da sind sich z.b. oben genannte Mainstream Sprachen auch einig. Nochmal kurz umrissen: Daten aufm Heap welche immutable sein können und ein Sprachmittel was auf diese Daten verweist. Jetzt kommt der Punkt welchen ich im vorherigen Post versucht hab zu verdeutlichen: Wieso ist die Immutabilität, die sich in funktionalen Sprachen auf Daten bezieht, in OOP angeblich nicht komplett implementiert wo sie sich dort doch auch auf die Daten die aufm Heap liegen bezieht und die Verweise auf die Daten korrekterweise natürlich nicht beinhaltet? Die Verweise auf Daten haben doch nichts mit den Daten an sich zu tun. Bei den Verweisen greift dagegen ein anderes Sprachmittel der OOP Sprachen: Konstantheit. Das hat in aber nichts mit Immutabilität zu tun. Es gibt da alle Varianten: variable Verweise auf mutable Objects, konstante Verweise auf mutable Objects, variable Verweise auf immutable Objects und konstante Verweise auf immutable Objects.
Wird dadurch vielleicht klarer warum ich auch so streng auf die Trennung von Object und Referenz geachtet hab? (Im)mutabilität ist ein Feature welches Objekte betrifft. Verweise auf diese Objekte dagegen fallen nicht in dieses Featre sondern sind ne Sache für sich. Selbst mit deinem "funktionalen Auge" versuchst du ein Feature breiter zu machen als es ist.
Über etwas mehr Substanz würde ich mich sehr freuen. Lass uns reden.
Vielleicht hat dieser Beitrag mehr Substanz in deinen Augen als der vorherige. Wäre nur schön wenn du in deinen eigenen Beiträgen auch deine Position erläuterst statt nur zu sagen: "Nein, deine Sichtweise ist falsch". Genau das hast du nämlich im vorherigen Beitrag getan, ohne auch nur ein Argument zu liefern welches zur Klärung der Diskussion beigeträgen hätte.
-
Talla schrieb:
Meine Kenntnisse von funktionalen Sprachen sind nicht arg ausgeprägt, aber bei rein funktionalen Sprachen arbeitet man ja allgemein mit Daten die in vielen Fällen immutable sind.
In rein funktionalen Sprachen ist es sogar so, dass *alle* Daten *immer* immutabel sind. Und obendrein gibt es auch keine Aliase, wodurch die Situation entsteht, dass nicht nur die Werte immutabel sind sondern in der Tat auch die Variablen, welche auf die Werte verweisen.
Und genau hier rührt m.E. das Missverständnis her: Sobald man allgemein Aliase einführt, muss man sich für eine Schiene entscheiden: entweder Aliase sind an einen Wert gebunden (immutabel) oder nicht. In pur funktionalen Sprachen sind sie (durch die Substitution) fest an einen Wert gebunden.
In modernen OO-Sprachen kann die Tatsache, dass es anders gehandhabt wird, durchaus irritieren, daher verstehe ich Maros anliegen. Nicht nur Anfänger sind gänzlich verwirrt von der Tatsache, dass sie dem String „s“ doch offensichtlich gerade einen neuen Wert zugewiesen haben – dann kann der ja wohl nicht immutable sein?! Wenn man da einmal hintergestiegen ist, kann man die beiden Konzepte natürlich auseinanderhalten. Allerdings tritt dann der Fall ein, dass durch das erneute Zuweisen an eine Variable in gewisser Weise ein degeneriertes Verhalten eintritt, nämlich das „Bezeichnet-Recycling“, was man vermeiden sollte.
Daher vermisse ich in VB und C# auch die Möglichkeit, die Java mir bietet, nämlich auch lokale Variablen als 'final' bzw. 'readonly' zu deklarieren. In meinen Java-Codes ist fast alles außer Laufvariablen 'final' deklariert.
Da gefällt mir Nemerle: hier ist es umgekehrt, *alle* Objekte sind immutable, außer, man kennzeichnet sie ausdrücklich als 'mutable'.
-
Das Anliegen war mir auch von Anfang an klar, aber man kann nicht von einem Programmierparadigma ein Konzept auf ein anderes Paradigma übertragen und nur weil es dort nicht den "Erwartungen" entspricht, sagen dass es unvollständig implementiert wurde.
Nicht nur Anfänger sind gänzlich verwirrt von der Tatsache, dass sie dem String „s“ doch offensichtlich gerade einen neuen Wert zugewiesen haben – dann kann der ja wohl nicht immutable sein?!
Das liegt aber nur an unsauberen Formulierungen und Erklärungen in Lehrwerken wie man sie leider viel zu oft sieht. Wenn man sowas hat
string s = "Hello World!"
und sagt s ist der String, dann ist das fachlich falsch! s ist die Referenz auf das Stringobjekt. Wenn man das einmal verinnerlicht hat, dann ist das Zuweisen von neuen Objekten an s obwohl das String Objekt immutable ist, alles andere als verwirrend. Klar hört man meistens "Der String s...", und bei Leuten denen die Konzepte der OOP klar sind, bereitet diese sprachliche Schlamperei keine Probleme, aber wenn man ein Feature erklärt, dass sich eindeutig auf Veränderlichkeit von Daten bezieht, darf man das einfach net so mehrdeutig formulieren, sondern muss klar unterscheiden.
Deshalb ist auch so eine Aussage:
Manchmal ging ich jedoch einen Schritt weiter, wie im Fall von Strings als Zeiger, da ich mich hier auf die Implementation bezog. Das ist pädagogisch nicht korrekt, sonst völlig richtig.
Nicht nur pädagogisch nicht korrekt, sondern führt gerade zu den Verwirrungen denen gerade vorgebeugt werden sollte.
-
Hallo Talla,
Talla schrieb:
Mir war bisher nicht bekannt das dieses Konzept aus den funktionalen Sprachen stammen soll.
Die definitive Klärung überlasse ich gern den Historikern. Tatsache ist: Funktionale Sprachen sind seit einer ganzen Weile da und eines ihrer zentralen Konzepte ist die Immutabilität. Bei Microsoft wird "hands on" über eine bessere Implementierung des Konzepts nachgedacht. Leute wie Eric Lippert (Senior Developer, C#-Compiler Team) und Joe Duffy (Program Manager, CLR-Team) treiben die Diskussion voran.
Talla schrieb:
Wird dadurch vielleicht klarer warum ich auch so streng auf die Trennung von Object und Referenz geachtet hab? (Im)mutabilität ist ein Feature welches Objekte betrifft. Verweise auf diese Objekte dagegen fallen nicht in dieses Featre sondern sind ne Sache für sich. Selbst mit deinem "funktionalen Auge" versuchst du ein Feature breiter zu machen als es ist.
Ich will hier nicht für Haskell oder F# missionieren. Konrad Rudolf hat's richtig erkannt: Worauf es mir ankommt, ist zu beschreiben, warum es für C#-Anfänger so verdammt schwer zu verstehen ist, dass Strings immutabel sind. Es rührt, meiner unwichtigen Meinung nach, vom Design der Sprache C# her.
Und: Wahre Immutabilität erreicht man leider auch nicht über das Schlüsselwort const, obwohl das Wörtchen manchmal Abhilfe schafft:
const string MEINNAME = "Gantenbein"; static void Main(string[] args) { const string meinName = GetName(); //CS0133 } static string GetName() { return MEINNAME; }
"Der Ausdruck, der 'Variable' zugewiesen wird, muss konstant sein", bemängelt der C#-Compiler in seiner unerschütterlichen Weisheit.
Hier einige interessante Links zum Thema:
http://blogs.msdn.com/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx
http://www.bluebytesoftware.com/blog/2007/11/11/ImmutableTypesForC.aspx
http://blogs.msdn.com/madst/archive/2007/01/23/is-c-becoming-a-functional-language.aspx
http://weblogs.asp.net/bleroy/archive/2008/01/16/immutability-in-c.aspx